Search code examples
javapdfbackgrounditextitext7

How to apply background color to table cell with round corners in iText7?


I am drawing the table with round corners. The table is dynamic and can grow to multiple pages. Now I want to apply some background color to cells. Each cell can have its own different color (not hard coded). But the issue is when I apply background color to corner cells, the color spills out from corners. The corner cells have their outer corners round. The implementation of round corner is done using this method https://stackoverflow.com/a/62764267/13446374 .

For implementing this I created a cell renderer which overrides the drawBackground() method to draw the expected results.

CellBackgroundColorRenderer.java

public class CellBackgroundColorRenderer extends CellRenderer {
    protected Color color;
    protected boolean isColoredBackground;
 
    public CellBackgroundColorRenderer(Cell modelElement, Color color, boolean isColoredBackground) {
        super(modelElement);
        this.color = color;
        this.isColoredBackground = isColoredBackground;
    }
 
    @Override
    public void drawBackground(DrawContext drawContext) {
        Rectangle rect = getOccupiedAreaBBox();
        PdfCanvas canvas = drawContext.getCanvas();
        
        // drawing white rectangle on top any pre existing background color. 
        canvas.saveState().rectangle(rect).setFillColor(new DeviceRgb(255, 255, 255)).fillStroke().restoreState();
        
        // drawing round corner rectangle for colored bg.
        canvas.saveState().roundRectangle(rect.getLeft(),rect.getBottom(), rect.getWidth(), rect.getHeight(), 5).setFillColor(color).fillStroke();
       
        // TODO fill color in those cell corners which are in conjuntion with other cells. 
        canvas.restoreState();
 
    }
}

TableBorderRenderer.java

public class TableBorderRenderer extends TableRenderer {
    
    private int numberOfColumns;
    public TableBorderRenderer(Table modelElement) {
        super(modelElement);
        numberOfColumns = modelElement.getNumberOfColumns();
    }

    @Override
    public IRenderer getNextRenderer() {
        return new TableBorderRenderer((Table) modelElement);
    }

    @Override
    public void draw(DrawContext drawContext) {

        CellRenderer[] cellRenderers = rows.get(0);
        
        Cell cell = (Cell) cellRenderers[0].getModelElement();
        ((Cell) cellRenderers[0].getModelElement()).setNextRenderer(new CellBackgroundColorRenderer((Cell) cellRenderers[0].getModelElement(), new DeviceRgb(255, 150, 255), true));
        
        ((Cell) cellRenderers[numberOfColumns-1].getModelElement()).setNextRenderer(new CellBackgroundColorRenderer((Cell) cellRenderers[numberOfColumns-1].getModelElement(), new DeviceRgb(255, 150, 255), true));

        super.draw(drawContext);
    }
    
    @Override
    protected void drawBorders(DrawContext drawContext) {
        Rectangle rect = getOccupiedAreaBBox();
        PdfPage currentPage = drawContext.getDocument().getPage(getOccupiedArea().getPageNumber());
        PdfCanvas aboveCanvas = new PdfCanvas(currentPage.newContentStreamAfter(), currentPage.getResources(), drawContext.getDocument());
        
        float lineWidth = 0.5f;
        rect.applyMargins(lineWidth / 2, lineWidth / 2, lineWidth / 2, lineWidth / 2, false);
        
        aboveCanvas.saveState().setStrokeColor(new DeviceRgb(255,255,255)).rectangle(rect).stroke().restoreState();
        aboveCanvas.saveState().setLineWidth(0.5f).setStrokeColor(new DeviceRgb(255,0,0)).roundRectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight(), 5).stroke().restoreState();

        super.drawBorders(drawContext);
    }
}

TableTest.java

public void createPdf(String dest) throws FileNotFoundException {
        
        PdfWriter writer = new PdfWriter(DEST);
        PdfDocument pdfDoc = new PdfDocument(writer);
        Document document = new Document(pdfDoc, PageSize.A4, false);
        
        Table table = new Table(3);
        for (int i=0; i < 100; i++) {
            for (int j=0; j < 3; j++) {
                table.addCell(new Cell().add(new Paragraph("Cell content")));
            }
        }
        table.setNextRenderer(new TableBorderRenderer(table));
        document.add(table);
        document.close();
        
}

In draw() of TableBorderRenderer I am setting the CellBackgroundColorRenderer to the modal element, i.e. cell. For now, I am setting the CellBackgroudnColorRenderer for first row only. But when I run the program, it doesn't even go in that drawBackground() of cellRenderer.

I am not sure what I am missing here. Is my approach correct? If not, can you guide me with the correct approach?

Thanks.


Solution

  • No need to manually draw the background color for cells - the functionality is already there:

    Table table = new Table(3);
    for (int i=0; i < 100; i++) {
        for (int j=0; j < 3; j++) {
            table.addCell(new Cell()
                    .setBackgroundColor(ColorConstants.GREEN)
                    .add(new Paragraph("Cell content")));
        }
    }
    table.setNextRenderer(new TableBorderRenderer(table));
    document.add(table);
    

    The only thing you need to do is to clip those backgrounds that are drawn outside of the rounded corners. You can overload drawChildren in TableRenderer for that:

    public class TableBorderRenderer extends TableRenderer {
    
        public TableBorderRenderer(Table modelElement) {
            super(modelElement);
        }
    
        @Override
        public IRenderer getNextRenderer() {
            return new TableBorderRenderer((Table) modelElement);
        }
    
        @Override
        protected void drawBorders(DrawContext drawContext) {
            Rectangle rect = getOccupiedAreaBBox();
            PdfPage currentPage = drawContext.getDocument().getPage(getOccupiedArea().getPageNumber());
            PdfCanvas aboveCanvas = new PdfCanvas(currentPage.newContentStreamAfter(), currentPage.getResources(), drawContext.getDocument());
    
            float lineWidth = 0.5f;
            rect.applyMargins(lineWidth, lineWidth, lineWidth, lineWidth, false);
    
            aboveCanvas.saveState().setLineWidth(lineWidth).setStrokeColor(new DeviceRgb(255,255,255)).rectangle(rect).stroke().restoreState();
            aboveCanvas.saveState().setLineWidth(lineWidth).setStrokeColor(new DeviceRgb(255,0,0)).roundRectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight(), 5).stroke().restoreState();
    
            super.drawBorders(drawContext);
        }
    
        @Override
        public void drawChildren(DrawContext drawContext) {
            Rectangle rect = getOccupiedAreaBBox();
            float lineWidth = 0.5f;
            rect.applyMargins(lineWidth, lineWidth, lineWidth, lineWidth, false);
    
            PdfCanvas canvas = drawContext.getCanvas();
            canvas.saveState();
            canvas.roundRectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight(), 5);
            canvas.clip().endPath();
            super.drawChildren(drawContext);
            canvas.restoreState();
        }
    }
    

    Visual result:

    result