Skip to main content
Skip table of contents

Table and cell events to draw borders

These examples were written in answer to questions such as:


celltitle

JAVA

JAVA
/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2023 Apryse Group NV
    Authors: Apryse Software.

    For more information, please contact iText Software at this address:
    sales@itextpdf.com
 */
package com.itextpdf.samples.sandbox.tables;

import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.properties.UnitValue;
import com.itextpdf.layout.renderer.CellRenderer;
import com.itextpdf.layout.renderer.DrawContext;
import com.itextpdf.layout.renderer.IRenderer;

import java.io.File;

public class CellTitle {
    public static final String DEST = "./target/sandbox/tables/cell_title.pdf";

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

        new CellTitle().manipulatePdf(DEST);
    }

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

        // By default column width is calculated automatically for the best fit.
        // useAllAvailableWidth() method makes table use the whole page's width while placing the content.
        Table table = new Table(UnitValue.createPercentArray(1)).useAllAvailableWidth();
        Cell cell = getCell("The title of this cell is title 1", "title 1");
        table.addCell(cell);
        cell = getCell("The title of this cell is title 2", "title 2");
        table.addCell(cell);
        cell = getCell("The title of this cell is title 3", "title 3");
        table.addCell(cell);
        doc.add(table);

        doc.close();
    }


    private static class CellTitleRenderer extends CellRenderer {
        protected String title;

        public CellTitleRenderer(Cell modelElement, String title) {
            super(modelElement);
            this.title = title;
        }

        // If a renderer overflows on the next area, iText uses #getNextRenderer() method to create a new renderer for the overflow part.
        // If #getNextRenderer() isn't overridden, the default method will be used and thus the default rather than the custom
        // renderer will be created
        @Override
        public IRenderer getNextRenderer() {
            return new CellTitleRenderer((Cell) modelElement, title);
        }

        @Override
        public void drawBorder(DrawContext drawContext) {
            PdfPage currentPage = drawContext.getDocument().getPage(getOccupiedArea().getPageNumber());

            // Create an above canvas in order to draw above borders.
            // Notice: bear in mind that iText draws cell borders on its TableRenderer level.
            PdfCanvas aboveCanvas = new PdfCanvas(currentPage.newContentStreamAfter(), currentPage.getResources(),
                    drawContext.getDocument());
            new Canvas(aboveCanvas, getOccupiedAreaBBox())
                    .add(new Paragraph(title)
                            .setMultipliedLeading(1)
                            .setMargin(0)
                            .setBackgroundColor(ColorConstants.LIGHT_GRAY)
                            .setFixedPosition(getOccupiedAreaBBox().getLeft() + 5,
                                    getOccupiedAreaBBox().getTop() - 8, 30));
        }
    }

    private static Cell getCell(String content, String title) {
        Cell cell = new Cell().add(new Paragraph(content));
        cell.setNextRenderer(new CellTitleRenderer(cell, title));
        cell.setPaddingTop(8).setPaddingBottom(8);
        return cell;
    }
}

C#

C#
using System;
using System.IO;
using iText.Kernel.Colors;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas;
using iText.Layout;
using iText.Layout.Element;
using iText.Layout.Properties;
using iText.Layout.Renderer;

namespace iText.Samples.Sandbox.Tables
{
    public class CellTitle
    {
        public static readonly string DEST = "results/sandbox/tables/cell_title.pdf";

        public static void Main(String[] args)
        {
            FileInfo file = new FileInfo(DEST);
            file.Directory.Create();

            new CellTitle().ManipulatePdf(DEST);
        }

        private void ManipulatePdf(String dest)
        {
            PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
            Document doc = new Document(pdfDoc);

            // By default column width is calculated automatically for the best fit.
            // useAllAvailableWidth() method set table to use the whole page's width while placing the content.
            Table table = new Table(UnitValue.CreatePercentArray(1)).UseAllAvailableWidth();
            Cell cell = GetCell("The title of this cell is title 1", "title 1");
            table.AddCell(cell);
            cell = GetCell("The title of this cell is title 2", "title 2");
            table.AddCell(cell);
            cell = GetCell("The title of this cell is title 3", "title 3");
            table.AddCell(cell);
            doc.Add(table);
            
            doc.Close();
        }

        private class CellTitleRenderer : CellRenderer
        {
            protected string title;

            public CellTitleRenderer(Cell modelElement, String title)
                : base(modelElement)
            {
                this.title = title;
            }            
            
            // If renderer overflows on the next area, iText uses getNextRender() method to create a renderer for the overflow part.
            // If getNextRenderer isn't overriden, the default method will be used and thus a default rather than custom
            // renderer will be created
            public override IRenderer GetNextRenderer()
            {
                return new CellTitleRenderer((Cell) modelElement, title);
            }

            public override void DrawBorder(DrawContext drawContext)
            {
                PdfPage currentPage = drawContext.GetDocument().GetPage(GetOccupiedArea().GetPageNumber());

                // Create an above canvas in order to draw above borders.
                // Notice: bear in mind that iText draws cell borders on its TableRenderer level.
                PdfCanvas aboveCanvas = new PdfCanvas(currentPage.NewContentStreamAfter(), currentPage.GetResources(), 
                    drawContext.GetDocument());
                new Canvas(aboveCanvas, GetOccupiedAreaBBox())
                    .Add(new Paragraph(title)
                        .SetMultipliedLeading(1)
                        .SetMargin(0)
                        .SetBackgroundColor(ColorConstants.LIGHT_GRAY)
                        .SetFixedPosition(GetOccupiedAreaBBox().GetLeft() + 5, 
                            GetOccupiedAreaBBox().GetTop() - 8, 30));
            }
        }

        private static Cell GetCell(string content, string title)
        {
            Cell cell = new Cell().Add(new Paragraph(content));
            cell.SetNextRenderer(new CellTitleRenderer(cell, title));
            cell.SetPaddingTop(8).SetPaddingBottom(8);
            return cell;
        }
    }
}


customborder

JAVA

JAVA
/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2023 Apryse Group NV
    Authors: Apryse Software.

    For more information, please contact iText Software at this address:
    sales@itextpdf.com
 */
package com.itextpdf.samples.sandbox.tables;

import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.borders.Border;
import com.itextpdf.layout.borders.SolidBorder;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.properties.BorderCollapsePropertyValue;
import com.itextpdf.layout.properties.Property;
import com.itextpdf.layout.properties.UnitValue;
import com.itextpdf.layout.renderer.CellRenderer;
import com.itextpdf.layout.renderer.DrawContext;
import com.itextpdf.layout.renderer.IRenderer;
import com.itextpdf.layout.renderer.TableRenderer;

import java.io.File;

public class CustomBorder {
    public static final String DEST = "./target/sandbox/tables/custom_border.pdf";

    public static final String TEXT = "This is some long paragraph\n" +
            "that will be added over and over\n" +
            "again to prove a point.\n" +
            "It should result in rows\n" +
            "that are split\n" +
            " and rows that aren't.";

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

        new CustomBorder().manipulatePdf(DEST);
    }

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

        // By default column width is calculated automatically for the best fit.
        // useAllAvailableWidth() method makes table use the whole page's width while placing the content.
        Table table = new Table(UnitValue.createPercentArray(2)).useAllAvailableWidth();

        // Fill a table with cells.
        // Be aware that by default all the cells will not have ay bottom borders
        for (int i = 1; i <= 60; i++) {
            table.addCell(new Cell().add(new Paragraph("Cell " + i)).setBorderBottom(Border.NO_BORDER));
            table.addCell(new Cell().add(new Paragraph(TEXT)).setBorderBottom(Border.NO_BORDER));
        }

        // We do not need a smart collapse logic: on the contrary, we want to override
        // processing of some cells, that's why we set SEPARATE value here
        table.setBorderCollapse(BorderCollapsePropertyValue.SEPARATE);

        // Use a custom renderer in which borders drawing is overridden
        table.setNextRenderer(new CustomBorderTableRenderer(table));

        doc.add(table);

        doc.close();
    }


    private static class CustomBorderTableRenderer extends TableRenderer {
        private boolean bottom = true;
        private boolean top = true;

        public CustomBorderTableRenderer(Table modelElement) {
            super(modelElement);
        }

        // If a renderer overflows on the next area, iText uses #getNextRenderer() method to create a new renderer for the overflow part.
        // If #getNextRenderer() isn't overridden, the default method will be used and thus the default rather than the custom
        // renderer will be created
        @Override
        public IRenderer getNextRenderer() {
            return new CustomBorderTableRenderer((Table) modelElement);
        }

        @Override
        protected TableRenderer[] split(int row, boolean hasContent, boolean cellWithBigRowspanAdded) {
            TableRenderer[] renderers = super.split(row, hasContent, cellWithBigRowspanAdded);

            // The first row of the split renderer represent the first rows of the current renderer
            ((CustomBorderTableRenderer) renderers[0]).top = top;

            // If there are some split cells, we should draw top borders of the overflow renderer
            // and bottom borders of the split renderer
            if (hasContent) {
                ((CustomBorderTableRenderer) renderers[0]).bottom = false;
                ((CustomBorderTableRenderer) renderers[1]).top = false;
            }
            return renderers;
        }

        @Override
        public void draw(DrawContext drawContext) {

            // If not set, iText will omit drawing of top borders
            if (!top) {
                for (CellRenderer cellRenderer : rows.get(0)) {
                    cellRenderer.setProperty(Property.BORDER_TOP, Border.NO_BORDER);
                }
            }

            // If set, iText will draw bottom borders
            if (bottom) {
                for (CellRenderer cellRenderer : rows.get(rows.size() - 1)) {
                    cellRenderer.setProperty(Property.BORDER_BOTTOM, new SolidBorder(0.5f));
                }
            }

            super.draw(drawContext);
        }
    }

}

C#

C#
using System;
using System.IO;
using iText.Kernel.Pdf;
using iText.Layout;
using iText.Layout.Borders;
using iText.Layout.Element;
using iText.Layout.Properties;
using iText.Layout.Renderer;

namespace iText.Samples.Sandbox.Tables
{
    public class CustomBorder
    {
        public static readonly string DEST = "results/sandbox/tables/custom_border.pdf";

        public static readonly string TEXT = "This is some long paragraph\n" +
                                             "that will be added over and over\n" +
                                             "again to prove a point.\n" +
                                             "It should result in rows\n" +
                                             "that are split\n" +
                                             " and rows that aren't.";

        public static void Main(String[] args)
        {
            FileInfo file = new FileInfo(DEST);
            file.Directory.Create();

            new CustomBorder().ManipulatePdf(DEST);
        }

        private void ManipulatePdf(string dest)
        {
            PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
            Document doc = new Document(pdfDoc);

            // By default column width is calculated automatically for the best fit.
            // useAllAvailableWidth() method set table to use the whole page's width while placing the content.
            Table table = new Table(UnitValue.CreatePercentArray(2)).UseAllAvailableWidth();

            // Fill a table with cells.
            // Be aware that by default all the cells will not have ay bottom borders
            for (int i = 1; i <= 60; i++) 
            {
                table.AddCell(new Cell().Add(new Paragraph("Cell " + i)).SetBorderBottom(Border.NO_BORDER));
                table.AddCell(new Cell().Add(new Paragraph(TEXT)).SetBorderBottom(Border.NO_BORDER));
            }

            // We do not need a smart collapse logic: on the contrary, we want to override
            // processing of some cells, that's why we set SEPARATE value here
            table.SetBorderCollapse(BorderCollapsePropertyValue.SEPARATE);

            // Use a custom renderer in which borders drawing is overridden
            table.SetNextRenderer(new CustomBorderTableRenderer(table));

            doc.Add(table);

            doc.Close();
        }

        private class CustomBorderTableRenderer : TableRenderer 
        {
            private bool bottom = true;
            private bool top = true;

            public CustomBorderTableRenderer(Table modelElement) : base(modelElement) 
            {   
            }            
            
            // If renderer overflows on the next area, iText uses getNextRender() method to create a renderer for the overflow part.
            // If getNextRenderer isn't overriden, the default method will be used and thus a default rather than custom
            // renderer will be created
            public override IRenderer GetNextRenderer() 
            {
                return new CustomBorderTableRenderer((Table)modelElement);
            }

            protected override TableRenderer[] Split(int row, bool hasContent, bool cellWithBigRowspanAdded) 
            {
                TableRenderer[] renderers = base.Split(row, hasContent, cellWithBigRowspanAdded); 

                // The first row of the split renderer represent the first rows of the current renderer
                ((CustomBorderTableRenderer)renderers[0]).top = top;

                // If there are some split cells, we should draw top borders of the overflow renderer
                // and bottom borders of the split renderer
                if (hasContent) 
                {
                    ((CustomBorderTableRenderer)renderers[0]).bottom = false;
                    ((CustomBorderTableRenderer)renderers[1]).top = false;
                }
                return renderers;
            }

            public override void Draw(DrawContext drawContext) 
            {

                // If not set, iText will omit drawing of top borders
                if (!top) 
                {
                    foreach (CellRenderer cellRenderer in rows[0]) 
                    {
                        cellRenderer.SetProperty(Property.BORDER_TOP, Border.NO_BORDER);
                    }
                }

                // If set, iText will draw bottom borders
                if (bottom) 
                {
                    foreach (CellRenderer cellRenderer in rows[rows.Count - 1])
                    {
                        cellRenderer.SetProperty(Property.BORDER_BOTTOM, new SolidBorder(0.5f));
                    }
                }

                base.Draw(drawContext);
            }
        }
    }
}

dottedlinecell

JAVA

JAVA
/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2023 Apryse Group NV
    Authors: Apryse Software.

    For more information, please contact iText Software at this address:
    sales@itextpdf.com
 */
package com.itextpdf.samples.sandbox.tables;

import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.borders.Border;
import com.itextpdf.layout.borders.DottedBorder;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.properties.UnitValue;
import com.itextpdf.layout.renderer.CellRenderer;
import com.itextpdf.layout.renderer.DrawContext;
import com.itextpdf.layout.renderer.IRenderer;
import com.itextpdf.layout.renderer.TableRenderer;

import java.io.File;

public class DottedLineCell {
    public static final String DEST = "./target/sandbox/tables/dotted_line_cell.pdf";

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

        new DottedLineCell().manipulatePdf(DEST);
    }

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

        doc.add(new Paragraph("Table event setter approach"));

        Table table = new Table(UnitValue.createPercentArray(3)).useAllAvailableWidth();
        table.addCell(createCellWithBorders("A1"));
        table.addCell(createCellWithBorders("A2"));
        table.addCell(createCellWithBorders("A3"));
        table.addCell(createCellWithBorders("B1"));
        table.addCell(createCellWithBorders("B2"));
        table.addCell(createCellWithBorders("B3"));
        table.addCell(createCellWithBorders("C1"));
        table.addCell(createCellWithBorders("C2"));
        table.addCell(createCellWithBorders("C3"));
        doc.add(table);

        doc.add(new Paragraph("Cell event setter approach"));

        table = new Table(UnitValue.createPercentArray(1)).useAllAvailableWidth();
        table.addCell(createCellWithBorders("Test"));

        doc.add(table);

        doc.add(new Paragraph("Table event custom render approach"));

        table = new Table(UnitValue.createPercentArray(3)).useAllAvailableWidth();
        table.addCell(new Cell().add(new Paragraph("A1")).setBorder(Border.NO_BORDER));
        table.addCell(new Cell().add(new Paragraph("A2")).setBorder(Border.NO_BORDER));
        table.addCell(new Cell().add(new Paragraph("A3")).setBorder(Border.NO_BORDER));
        table.addCell(new Cell().add(new Paragraph("B1")).setBorder(Border.NO_BORDER));
        table.addCell(new Cell().add(new Paragraph("B2")).setBorder(Border.NO_BORDER));
        table.addCell(new Cell().add(new Paragraph("B3")).setBorder(Border.NO_BORDER));
        table.addCell(new Cell().add(new Paragraph("C1")).setBorder(Border.NO_BORDER));
        table.addCell(new Cell().add(new Paragraph("C2")).setBorder(Border.NO_BORDER));
        table.addCell(new Cell().add(new Paragraph("C3")).setBorder(Border.NO_BORDER));

        // Draws dotted line borders.
        table.setNextRenderer(new DottedLineTableRenderer(table));

        doc.add(table);

        doc.add(new Paragraph("Cell event custom render approach"));

        table = new Table(UnitValue.createPercentArray(1)).useAllAvailableWidth();

        Cell cell = new Cell().add(new Paragraph("Test"));
        cell.setNextRenderer(new DottedLineCellRenderer(cell));

        // Since we override the border drawing, we do not need the default logic to be triggered, that's why we set
        // the border value as null
        cell.setBorder(Border.NO_BORDER);
        table.addCell(cell);

        doc.add(table);

        doc.close();
    }


    private static class DottedLineTableRenderer extends TableRenderer {
        public DottedLineTableRenderer(Table modelElement) {
            super(modelElement);
        }

        // If a renderer overflows on the next area, iText uses #getNextRenderer() method to create a new renderer for the overflow part.
        // If #getNextRenderer() isn't overridden, the default method will be used and thus the default rather than the custom
        // renderer will be created
        @Override
        public IRenderer getNextRenderer() {
            return new DottedLineTableRenderer((Table) modelElement);
        }

        @Override
        public void drawChildren(DrawContext drawContext) {
            super.drawChildren(drawContext);
            PdfCanvas canvas = drawContext.getCanvas();
            canvas.setLineDash(1f, 3f);

            // first horizontal line
            CellRenderer[] cellRenderers = rows.get(0);
            canvas.moveTo(cellRenderers[0].getOccupiedArea().getBBox().getLeft(),
                    cellRenderers[0].getOccupiedArea().getBBox().getTop());
            canvas.lineTo(cellRenderers[cellRenderers.length - 1].getOccupiedArea().getBBox().getRight(),
                    cellRenderers[cellRenderers.length - 1].getOccupiedArea().getBBox().getTop());

            for (CellRenderer[] renderers : rows) {

                // horizontal lines
                canvas.moveTo(renderers[0].getOccupiedArea().getBBox().getX(),
                        renderers[0].getOccupiedArea().getBBox().getY());
                canvas.lineTo(renderers[renderers.length - 1].getOccupiedArea().getBBox().getRight(),
                        renderers[renderers.length - 1].getOccupiedArea().getBBox().getBottom());

                // first vertical line
                Rectangle cellRect = renderers[0].getOccupiedArea().getBBox();
                canvas.moveTo(cellRect.getLeft(), cellRect.getBottom());
                canvas.lineTo(cellRect.getLeft(), cellRect.getTop());

                // vertical lines
                for (CellRenderer renderer : renderers) {
                    cellRect = renderer.getOccupiedArea().getBBox();
                    canvas.moveTo(cellRect.getRight(), cellRect.getBottom());
                    canvas.lineTo(cellRect.getRight(), cellRect.getTop());
                }
            }

            canvas.stroke();
        }
    }

    private static class DottedLineCellRenderer extends CellRenderer {
        public DottedLineCellRenderer(Cell modelElement) {
            super(modelElement);
        }

        // If a renderer overflows on the next area, iText uses #getNextRenderer() method to create a new renderer for the overflow part.
        // If #getNextRenderer() isn't overridden, the default method will be used and thus the default rather than the custom
        // renderer will be created
        @Override
        public IRenderer getNextRenderer() {
            return new DottedLineCellRenderer((Cell) modelElement);
        }

        @Override
        public void draw(DrawContext drawContext) {
            super.draw(drawContext);
            drawContext.getCanvas().setLineDash(1f, 3f);
            drawContext.getCanvas().rectangle(this.getOccupiedArea().getBBox());
            drawContext.getCanvas().stroke();
        }
    }

    private Cell createCellWithBorders(String content) {
        Cell cell = new Cell().add(new Paragraph(content));
        cell.setBorder(new DottedBorder(1));

        return cell;
    }
}

C#

C#
using System;
using System.IO;
using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas;
using iText.Layout;
using iText.Layout.Element;
using iText.Layout.Properties;
using iText.Layout.Renderer;

namespace iText.Samples.Sandbox.Tables
{
    public class CustomBorder2
    {
        public static readonly string DEST = "results/sandbox/tables/custom_border2.pdf";

        public static readonly string TEXT = "This is some long paragraph that will be added over and over " +
                                             "again to prove a point. It should result in rows that are split and rows that aren't.";

        public static void Main(String[] args)
        {
            FileInfo file = new FileInfo(DEST);
            file.Directory.Create();

            new CustomBorder2().ManipulatePdf(DEST);
        }

        private void ManipulatePdf(string dest)
        {
            PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
            Document doc = new Document(pdfDoc);

            // By default column width is calculated automatically for the best fit.
            // useAllAvailableWidth() method makes table use the whole page's width while placing the content.
            Table table = new Table(UnitValue.CreatePercentArray(2)).UseAllAvailableWidth();

            for (int i = 1; i < 60; i++) 
            {
                table.AddCell(new Cell().Add(new Paragraph("Cell " + i)));
                table.AddCell(new Cell().Add(new Paragraph(TEXT)));
            }

            // Use a custom renderer in which borders drawing is overridden
            table.SetNextRenderer(new CustomBorder2TableRenderer(table));

            doc.Add(table);

            doc.Close();
        }

        private class CustomBorder2TableRenderer : TableRenderer
        {
            private bool top = true;
            private bool bottom = true;

            public CustomBorder2TableRenderer(Table modelElement) : base(modelElement)
            {
            }            
            
            // If renderer overflows on the next area, iText uses getNextRender() method to create a renderer for the overflow part.
            // If getNextRenderer isn't overriden, the default method will be used and thus a default rather than custom
            // renderer will be created
            public override IRenderer GetNextRenderer()
            {
                return new CustomBorder2TableRenderer((Table) modelElement);
            }

            protected override TableRenderer[] Split(int row, bool hasContent, bool cellWithBigRowspanAdded)
            {
                // Being inside this method implies that split has occurred

                TableRenderer[] results = base.Split(row, hasContent, cellWithBigRowspanAdded);

                CustomBorder2TableRenderer splitRenderer = (CustomBorder2TableRenderer)results[0];

                // iText shouldn't draw the bottom split renderer's border,
                // because there is an overflow renderer
                splitRenderer.bottom = false;

                // If top is true, the split renderer is the first renderer of the document.
                // If false, iText has already processed the first renderer
                splitRenderer.top = this.top;

                CustomBorder2TableRenderer overflowRenderer = (CustomBorder2TableRenderer)results[1];

                // iText shouldn't draw the top overflow renderer's border
                overflowRenderer.top = false;

                return results;
            }

            protected override void DrawBorders(DrawContext drawContext)
            {
                Rectangle area = occupiedArea.GetBBox();
                PdfCanvas canvas = drawContext.GetCanvas();

                canvas
                    .SaveState()
                    .MoveTo(area.GetLeft(), area.GetBottom())
                    .LineTo(area.GetLeft(), area.GetTop())
                    .MoveTo(area.GetRight(), area.GetTop())
                    .LineTo(area.GetRight(), area.GetBottom());

                if (top) 
                {
                    canvas
                        .MoveTo(area.GetLeft(), area.GetTop())
                        .LineTo(area.GetRight(), area.GetTop());
                }

                if (bottom) 
                {
                    canvas
                        .MoveTo(area.GetLeft(), area.GetBottom())
                        .LineTo(area.GetRight(), area.GetBottom());
                }

                canvas
                    .Stroke()
                    .RestoreState();
            }
        }
    }
}

dottedlinecell2

JAVA

JAVA
/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2023 Apryse Group NV
    Authors: Apryse Software.

    For more information, please contact iText Software at this address:
    sales@itextpdf.com
 */
package com.itextpdf.samples.sandbox.tables;

import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.Style;
import com.itextpdf.layout.borders.Border;
import com.itextpdf.layout.borders.DottedBorder;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.properties.UnitValue;
import com.itextpdf.layout.renderer.CellRenderer;
import com.itextpdf.layout.renderer.DrawContext;
import com.itextpdf.layout.renderer.IRenderer;

import java.io.File;

public class DottedLineCell2 {
    public static final String DEST = "./target/sandbox/tables/dotted_line_cell2.pdf";

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

        new DottedLineCell2().manipulatePdf(DEST);
    }

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

        Paragraph paragraph = new Paragraph("Setter approach");
        document.add(paragraph.setFontSize(25));

        Table table = new Table(UnitValue.createPercentArray(4)).useAllAvailableWidth();
        table.setMarginBottom(30);

        table.addCell(createCell("left border", new Style().setBorderLeft(new DottedBorder(1))));
        table.addCell(createCell("right border", new Style().setBorderRight(new DottedBorder(1))));
        table.addCell(createCell("top border", new Style().setBorderTop(new DottedBorder(1))));
        table.addCell(createCell("bottom border", new Style().setBorderBottom(new DottedBorder(1))));

        document.add(table);

        table = new Table(UnitValue.createPercentArray(4)).useAllAvailableWidth();
        table.setMarginBottom(30);

        table.addCell(createCell("left and top border", new Style()
                .setBorderLeft(new DottedBorder(1))
                .setBorderTop(new DottedBorder(1))));
        table.addCell(createCell("right and bottom border", new Style()
                .setBorderRight(new DottedBorder(1))
                .setBorderBottom(new DottedBorder(1))));
        table.addCell(createCell("no border", new Style()));
        table.addCell(createCell("full border", new Style()
                .setBorderBottom(new DottedBorder(1))
                .setBorderTop(new DottedBorder(1))
                .setBorderRight(new DottedBorder(1))
                .setBorderLeft(new DottedBorder(1))));

        document.add(table);

        paragraph = new Paragraph("Custom render approach");
        document.add(paragraph.setFontSize(25));

        table = new Table(UnitValue.createPercentArray(4)).useAllAvailableWidth();
        table.setMarginBottom(30);

        Cell cell = new Cell().add(new Paragraph("left border"));
        cell.setBorder(Border.NO_BORDER);
        cell.setNextRenderer(new DottedLineCellRenderer(cell, new boolean[] {false, true, false, false}));
        table.addCell(cell);

        cell = new Cell().add(new Paragraph("right border"));
        cell.setBorder(Border.NO_BORDER);
        cell.setNextRenderer(new DottedLineCellRenderer(cell, new boolean[] {false, false, false, true}));
        table.addCell(cell);

        cell = new Cell().add(new Paragraph("top border"));
        cell.setBorder(Border.NO_BORDER);
        cell.setNextRenderer(new DottedLineCellRenderer(cell, new boolean[] {true, false, false, false}));
        table.addCell(cell);

        cell = new Cell().add(new Paragraph("bottom border"));
        cell.setBorder(Border.NO_BORDER);
        cell.setNextRenderer(new DottedLineCellRenderer(cell, new boolean[] {false, false, true, false}));
        table.addCell(cell);

        document.add(table);

        table = new Table(UnitValue.createPercentArray(4)).useAllAvailableWidth();
        table.setMarginBottom(30);

        cell = new Cell().add(new Paragraph("left and top border"));
        cell.setBorder(Border.NO_BORDER);
        cell.setNextRenderer(new DottedLineCellRenderer(cell, new boolean[] {true, true, false, false}));
        table.addCell(cell);

        cell = new Cell().add(new Paragraph("right and bottom border"));
        cell.setBorder(Border.NO_BORDER);
        cell.setNextRenderer(new DottedLineCellRenderer(cell, new boolean[] {false, false, true, true}));
        table.addCell(cell);

        cell = new Cell().add(new Paragraph("no border"));
        cell.setBorder(Border.NO_BORDER);
        table.addCell(cell);

        cell = new Cell().add(new Paragraph("full border"));
        cell.setBorder(Border.NO_BORDER);
        cell.setNextRenderer(new DottedLineCellRenderer(cell, new boolean[] {true, true, true, true}));
        table.addCell(cell);

        document.add(table);

        document.close();
    }

    private static class DottedLineCellRenderer extends CellRenderer {
        boolean[] borders;

        public DottedLineCellRenderer(Cell modelElement, boolean[] borders) {
            super(modelElement);
            this.borders = new boolean[borders.length];

            for (int i = 0; i < this.borders.length; i++) {
                this.borders[i] = borders[i];
            }
        }

        // If a renderer overflows on the next area, iText uses #getNextRenderer() method to create a new renderer for the overflow part.
        // If #getNextRenderer() isn't overridden, the default method will be used and thus the default rather than the custom
        // renderer will be created
        @Override
        public IRenderer getNextRenderer() {
            return new DottedLineCellRenderer((Cell) modelElement, borders);
        }

        @Override
        public void draw(DrawContext drawContext) {
            super.draw(drawContext);
            PdfCanvas canvas = drawContext.getCanvas();
            Rectangle position = getOccupiedAreaBBox();
            canvas.saveState();
            canvas.setLineDash(1f,3f);

            if (borders[0]) {
                canvas.moveTo(position.getRight(), position.getTop());
                canvas.lineTo(position.getLeft(), position.getTop());
            }

            if (borders[2]) {
                canvas.moveTo(position.getRight(), position.getBottom());
                canvas.lineTo(position.getLeft(), position.getBottom());
            }

            if (borders[3]) {
                canvas.moveTo(position.getRight(), position.getTop());
                canvas.lineTo(position.getRight(), position.getBottom());
            }

            if (borders[1]) {
                canvas.moveTo(position.getLeft(), position.getTop());
                canvas.lineTo(position.getLeft(), position.getBottom());
            }

            canvas.stroke();
            canvas.restoreState();
        }
    }

    private static Cell createCell(String content, Style style) {
        Cell cell = new Cell()
                .add(new Paragraph(content))

                // By default there is a BORDER property set as SolidBorder. We want to override it
                // and that's why this property is set to null.
                // However, if there is a BORDER property in the passed Style instance,
                // it will be used because it's added afterwards.
                .addStyle(new Style().setBorder(Border.NO_BORDER));

        cell.addStyle(style);

        return cell;
    }
}

C#

C#
using System;
using System.IO;
using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas;
using iText.Layout;
using iText.Layout.Borders;
using iText.Layout.Element;
using iText.Layout.Properties;
using iText.Layout.Renderer;

namespace iText.Samples.Sandbox.Tables 
{
    public class DottedLineCell2 
    {
        public static readonly string DEST = "results/sandbox/tables/dotted_line_cell2.pdf";

        public static void Main(String[] args) 
        {
            FileInfo file = new FileInfo(DEST);
            file.Directory.Create();
            
            new DottedLineCell2().ManipulatePdf(DEST);
        }

        private void ManipulatePdf(String dest) 
        {
            PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
            Document document = new Document(pdfDoc);
            
            Paragraph paragraph = new Paragraph("Setter approach");
            document.Add(paragraph.SetFontSize(25));
            
            Table table = new Table(UnitValue.CreatePercentArray(4)).UseAllAvailableWidth();
            table.SetMarginBottom(30);
            
            table.AddCell(CreateCell("left border", new Style().SetBorderLeft(new DottedBorder(1))));
            table.AddCell(CreateCell("right border", new Style().SetBorderRight(new DottedBorder(1))));
            table.AddCell(CreateCell("top border", new Style().SetBorderTop(new DottedBorder(1))));
            table.AddCell(CreateCell("bottom border", new Style().SetBorderBottom(new DottedBorder(1))));
            
            document.Add(table);
            
            table = new Table(UnitValue.CreatePercentArray(4)).UseAllAvailableWidth();
            table.SetMarginBottom(30);
            
            table.AddCell(CreateCell("left and top border", new Style()
                    .SetBorderLeft(new DottedBorder(1))
                    .SetBorderTop(new DottedBorder(1))));
            table.AddCell(CreateCell("right and bottom border", new Style()
                    .SetBorderRight(new DottedBorder(1))
                    .SetBorderBottom(new DottedBorder(1))));
            table.AddCell(CreateCell("no border", new Style()));
            table.AddCell(CreateCell("full border", new Style()
                    .SetBorderBottom(new DottedBorder(1))
                    .SetBorderTop(new DottedBorder(1))
                    .SetBorderRight(new DottedBorder(1))
                    .SetBorderLeft(new DottedBorder(1))));
            
            document.Add(table);
            
            paragraph = new Paragraph("Custom render approach");
            document.Add(paragraph.SetFontSize(25));
            
            table = new Table(UnitValue.CreatePercentArray(4)).UseAllAvailableWidth();
            table.SetMarginBottom(30);
            
            Cell cell = new Cell().Add(new Paragraph("left border"));
            cell.SetBorder(Border.NO_BORDER);
            cell.SetNextRenderer(new DottedLineCellRenderer(cell, new bool[] { false, true, false, false}));
            table.AddCell(cell);
            
            cell = new Cell().Add(new Paragraph("right border"));
            cell.SetBorder(Border.NO_BORDER);
            cell.SetNextRenderer(new DottedLineCellRenderer(cell, new bool[] { false, false, false, true}));
            table.AddCell(cell);
            
            cell = new Cell().Add(new Paragraph("top border"));
            cell.SetBorder(Border.NO_BORDER);
            cell.SetNextRenderer(new DottedLineCellRenderer(cell, new bool[] { true, false, false, false}));
            table.AddCell(cell);
            
            cell = new Cell().Add(new Paragraph("bottom border"));
            cell.SetBorder(Border.NO_BORDER);
            cell.SetNextRenderer(new DottedLineCellRenderer(cell, new bool[] { false, false, true, false}));
            table.AddCell(cell);
            
            document.Add(table);
            
            table = new Table(UnitValue.CreatePercentArray(4)).UseAllAvailableWidth();
            table.SetMarginBottom(30);
            
            cell = new Cell().Add(new Paragraph("left and top border"));
            cell.SetBorder(Border.NO_BORDER);
            cell.SetNextRenderer(new DottedLineCellRenderer(cell, new bool[] { true, true, false, false}));
            table.AddCell(cell);
            
            cell = new Cell().Add(new Paragraph("right and bottom border"));
            cell.SetBorder(Border.NO_BORDER);
            cell.SetNextRenderer(new DottedLineCellRenderer(cell, new bool[] { false, false, true, true}));
            table.AddCell(cell);
            
            cell = new Cell().Add(new Paragraph("no border"));
            cell.SetBorder(Border.NO_BORDER);
            table.AddCell(cell);
            
            cell = new Cell().Add(new Paragraph("full border"));
            cell.SetBorder(Border.NO_BORDER);
            cell.SetNextRenderer(new DottedLineCellRenderer(cell, new bool[] { true, true, true, true }));
            table.AddCell(cell);
            
            document.Add(table);
            
            document.Close();
        }

        private class DottedLineCellRenderer : CellRenderer 
        {
            internal bool[] borders;

            public DottedLineCellRenderer(Cell modelElement, bool[] borders)
                : base(modelElement) 
            {
                this.borders = new bool[borders.Length];
                for (int i = 0; i < this.borders.Length; i++) 
                {
                    this.borders[i] = borders[i];
                }
            }

            // If renderer overflows on the next area, iText uses getNextRender() method to create a renderer for the overflow part.
            // If getNextRenderer isn't overriden, the default method will be used and thus a default rather than custom
            // renderer will be created
            public override IRenderer GetNextRenderer() 
            {
                return new DottedLineCellRenderer((Cell)modelElement, borders);
            }

            public override void Draw(DrawContext drawContext) 
            {
                base.Draw(drawContext);
                PdfCanvas canvas = drawContext.GetCanvas();
                Rectangle position = GetOccupiedAreaBBox();
                canvas.SaveState();
                canvas.SetLineDash(1f, 3f);
                
                if (borders[0]) 
                {
                    canvas.MoveTo(position.GetRight(), position.GetTop());
                    canvas.LineTo(position.GetLeft(), position.GetTop());
                }
                
                if (borders[2]) 
                {
                    canvas.MoveTo(position.GetRight(), position.GetBottom());
                    canvas.LineTo(position.GetLeft(), position.GetBottom());
                }
                
                if (borders[3]) 
                {
                    canvas.MoveTo(position.GetRight(), position.GetTop());
                    canvas.LineTo(position.GetRight(), position.GetBottom());
                }
                
                if (borders[1]) 
                {
                    canvas.MoveTo(position.GetLeft(), position.GetTop());
                    canvas.LineTo(position.GetLeft(), position.GetBottom());
                }
                
                canvas.Stroke();
                canvas.RestoreState();
            }
        }

        private static Cell CreateCell(String content, Style style) 
        {
            Cell cell = new Cell()
                    .Add(new Paragraph(content))

                    // By default there is a BORDER property set as SolidBorder. We want to override it
                    // and that's why this property is set to null.
                    // However, if there is a BORDER property in the passed Style instance,
                    // it will be used because it's added afterwards.                
                    .AddStyle(new Style().SetBorder(Border.NO_BORDER));
            
            cell.AddStyle(style);

            return cell;
        }
    }
}


dottedlineheader

JAVA

JAVA
/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2023 Apryse Group NV
    Authors: Apryse Software.

    For more information, please contact iText Software at this address:
    sales@itextpdf.com
 */
package com.itextpdf.samples.sandbox.tables;

import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.Style;
import com.itextpdf.layout.borders.Border;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.properties.UnitValue;
import com.itextpdf.layout.renderer.CellRenderer;
import com.itextpdf.layout.renderer.DrawContext;
import com.itextpdf.layout.renderer.IRenderer;
import com.itextpdf.layout.renderer.TableRenderer;

import java.io.File;

public class DottedLineHeader {
    public static final String DEST = "./target/sandbox/tables/dotted_line_header.pdf";

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

        new DottedLineHeader().manipulatePdf(DEST);
    }

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

        Table table = new Table(UnitValue.createPercentArray(3)).useAllAvailableWidth();

        // Draws dotted line border in table
        table.setNextRenderer(new DottedHeaderTableRenderer(table, new Table.RowRange(0, 1)));

        Style noBorder = new Style().setBorder(Border.NO_BORDER);

        table.addHeaderCell(new Cell().add(new Paragraph("A1")).addStyle(noBorder));
        table.addHeaderCell(new Cell().add(new Paragraph("A2")).addStyle(noBorder));
        table.addHeaderCell(new Cell().add(new Paragraph("A3")).addStyle(noBorder));
        table.addCell(new Cell().add(new Paragraph("B1")).addStyle(noBorder));
        table.addCell(new Cell().add(new Paragraph("B2")).addStyle(noBorder));
        table.addCell(new Cell().add(new Paragraph("B3")).addStyle(noBorder));
        table.addCell(new Cell().add(new Paragraph("C1")).addStyle(noBorder));
        table.addCell(new Cell().add(new Paragraph("C2")).addStyle(noBorder));
        table.addCell(new Cell().add(new Paragraph("C3")).addStyle(noBorder));

        doc.add(table);
        doc.add(new Paragraph("Cell event"));

        table = new Table(UnitValue.createPercentArray(3)).useAllAvailableWidth();
        Cell cell = new Cell().add(new Paragraph("A1")).addStyle(noBorder);

        // Draws dotted line border in cell
        cell.setNextRenderer(new DottedHeaderCellRenderer(cell));
        table.addCell(cell);

        cell = new Cell().add(new Paragraph("A2")).addStyle(noBorder);
        cell.setNextRenderer(new DottedHeaderCellRenderer(cell));
        table.addCell(cell);

        cell = new Cell().add(new Paragraph("A3")).addStyle(noBorder);
        cell.setNextRenderer(new DottedHeaderCellRenderer(cell));
        table.addCell(cell);

        table.addCell(new Cell().add(new Paragraph("B1")).addStyle(noBorder));
        table.addCell(new Cell().add(new Paragraph("B2")).addStyle(noBorder));
        table.addCell(new Cell().add(new Paragraph("B3")).addStyle(noBorder));
        table.addCell(new Cell().add(new Paragraph("C1")).addStyle(noBorder));
        table.addCell(new Cell().add(new Paragraph("C2")).addStyle(noBorder));
        table.addCell(new Cell().add(new Paragraph("C3")).addStyle(noBorder));

        doc.add(table);

        doc.close();
    }


    private static class DottedHeaderTableRenderer extends TableRenderer {
        public DottedHeaderTableRenderer(Table modelElement, Table.RowRange rowRange) {
            super(modelElement, rowRange);
        }

        // If a renderer overflows on the next area, iText uses #getNextRenderer() method to create a new renderer for the overflow part.
        // If #getNextRenderer() isn't overridden, the default method will be used and thus the default rather than the custom
        // renderer will be created
        @Override
        public IRenderer getNextRenderer() {
            return new DottedHeaderTableRenderer((Table) modelElement, rowRange);
        }

        @Override
        public void drawChildren(DrawContext drawContext) {
            super.drawChildren(drawContext);
            Rectangle headersArea = headerRenderer.getOccupiedArea().getBBox();
            PdfCanvas canvas = drawContext.getCanvas();

            canvas.setLineDash(3f, 3f);
            canvas.moveTo(headersArea.getLeft(), headersArea.getTop());
            canvas.lineTo(headersArea.getRight(), headersArea.getTop());
            canvas.moveTo(headersArea.getLeft(), headersArea.getBottom());
            canvas.lineTo(headersArea.getRight(), headersArea.getBottom());
            canvas.stroke();
        }
    }


    private static class DottedHeaderCellRenderer extends CellRenderer {
        public DottedHeaderCellRenderer(Cell modelElement) {
            super(modelElement);
        }

        // If a renderer overflows on the next area, iText uses #getNextRenderer() method to create a new renderer for the overflow part.
        // If #getNextRenderer() isn't overridden, the default method will be used and thus the default rather than the custom
        // renderer will be created
        @Override
        public IRenderer getNextRenderer() {
            return new DottedHeaderCellRenderer((Cell) modelElement);
        }

        @Override
        public void draw(DrawContext drawContext) {
            super.draw(drawContext);
            PdfCanvas canvas = drawContext.getCanvas();
            Rectangle bbox = getOccupiedArea().getBBox();
            
            canvas.setLineDash(3f, 3f);
            canvas.moveTo(bbox.getLeft(), bbox.getBottom());
            canvas.lineTo(bbox.getRight(), bbox.getBottom());
            canvas.moveTo(bbox.getLeft(), bbox.getTop());
            canvas.lineTo(bbox.getRight(), bbox.getTop());
            canvas.stroke();
        }
    }
}

C#

C#
using System;
using System.IO;
using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas;
using iText.Layout;
using iText.Layout.Borders;
using iText.Layout.Element;
using iText.Layout.Properties;
using iText.Layout.Renderer;

namespace iText.Samples.Sandbox.Tables
{
    public class DottedLineHeader
    {
        public static readonly string DEST = "results/sandbox/tables/dotted_line_header.pdf";

        public static void Main(String[] args)
        {
            FileInfo file = new FileInfo(DEST);
            file.Directory.Create();

            new DottedLineHeader().ManipulatePdf(DEST);
        }

        private void ManipulatePdf(string dest)
        {
            PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
            Document doc = new Document(pdfDoc);

            Table table = new Table(UnitValue.CreatePercentArray(3)).UseAllAvailableWidth();

            // Draws dotted line border in table
            table.SetNextRenderer(new DottedHeaderTableRenderer(table, new Table.RowRange(0, 1)));

            Style noBorder = new Style().SetBorder(Border.NO_BORDER);

            table.AddHeaderCell(new Cell().Add(new Paragraph("A1")).AddStyle(noBorder));
            table.AddHeaderCell(new Cell().Add(new Paragraph("A2")).AddStyle(noBorder));
            table.AddHeaderCell(new Cell().Add(new Paragraph("A3")).AddStyle(noBorder));
            table.AddCell(new Cell().Add(new Paragraph("B1")).AddStyle(noBorder));
            table.AddCell(new Cell().Add(new Paragraph("B2")).AddStyle(noBorder));
            table.AddCell(new Cell().Add(new Paragraph("B3")).AddStyle(noBorder));
            table.AddCell(new Cell().Add(new Paragraph("C1")).AddStyle(noBorder));
            table.AddCell(new Cell().Add(new Paragraph("C2")).AddStyle(noBorder));
            table.AddCell(new Cell().Add(new Paragraph("C3")).AddStyle(noBorder));

            doc.Add(table);
            doc.Add(new Paragraph("Cell event"));

            table = new Table(UnitValue.CreatePercentArray(3)).UseAllAvailableWidth();
            Cell cell = new Cell().Add(new Paragraph("A1")).AddStyle(noBorder);

            // Draws dotted line border in cell
            cell.SetNextRenderer(new DottedHeaderCellRenderer(cell));
            table.AddCell(cell);

            cell = new Cell().Add(new Paragraph("A2")).AddStyle(noBorder);
            cell.SetNextRenderer(new DottedHeaderCellRenderer(cell));
            table.AddCell(cell);

            cell = new Cell().Add(new Paragraph("A3")).AddStyle(noBorder);
            cell.SetNextRenderer(new DottedHeaderCellRenderer(cell));
            table.AddCell(cell);

            table.AddCell(new Cell().Add(new Paragraph("B1")).AddStyle(noBorder));
            table.AddCell(new Cell().Add(new Paragraph("B2")).AddStyle(noBorder));
            table.AddCell(new Cell().Add(new Paragraph("B3")).AddStyle(noBorder));
            table.AddCell(new Cell().Add(new Paragraph("C1")).AddStyle(noBorder));
            table.AddCell(new Cell().Add(new Paragraph("C2")).AddStyle(noBorder));
            table.AddCell(new Cell().Add(new Paragraph("C3")).AddStyle(noBorder));

            doc.Add(table);

            doc.Close();
        }

        private class DottedHeaderTableRenderer : TableRenderer
        {
            public DottedHeaderTableRenderer(Table modelElement, Table.RowRange rowRange)
                : base(modelElement, rowRange)
            {
            }            
            
            // If renderer overflows on the next area, iText uses getNextRender() method to create a renderer for the overflow part.
            // If getNextRenderer isn't overriden, the default method will be used and thus a default rather than custom
            // renderer will be created
            public override IRenderer GetNextRenderer()
            {
                return new DottedHeaderTableRenderer((Table) modelElement, rowRange);
            }

            public override void DrawChildren(DrawContext drawContext)
            {
                base.DrawChildren(drawContext);
                PdfCanvas canvas = drawContext.GetCanvas();
                Rectangle headersArea = headerRenderer.GetOccupiedArea().GetBBox();
                
                canvas.SetLineDash(3f, 3f);
                canvas.MoveTo(headersArea.GetLeft(), headersArea.GetTop());
                canvas.LineTo(headersArea.GetRight(), headersArea.GetTop());
                canvas.MoveTo(headersArea.GetLeft(), headersArea.GetBottom());
                canvas.LineTo(headersArea.GetRight(), headersArea.GetBottom());
                canvas.Stroke();
            }
        }

        private class DottedHeaderCellRenderer : CellRenderer
        {
            public DottedHeaderCellRenderer(Cell modelElement)
                : base(modelElement)
            {
            }            
            
            // If renderer overflows on the next area, iText uses getNextRender() method to create a renderer for the overflow part.
            // If getNextRenderer isn't overriden, the default method will be used and thus a default rather than custom
            // renderer will be created
            public override IRenderer GetNextRenderer()
            {
                return new DottedHeaderCellRenderer((Cell) modelElement);
            }

            public override void Draw(DrawContext drawContext)
            {
                base.Draw(drawContext);
                PdfCanvas canvas = drawContext.GetCanvas();
                Rectangle bbox = GetOccupiedArea().GetBBox();

                canvas.SetLineDash(3f, 3f);
                canvas.MoveTo(bbox.GetLeft(), bbox.GetBottom());
                canvas.LineTo(bbox.GetRight(), bbox.GetBottom());
                canvas.MoveTo(bbox.GetLeft(), bbox.GetTop());
                canvas.LineTo(bbox.GetRight(), bbox.GetTop());
                canvas.Stroke();
            }
        }
    }
}

roundedcorners

JAVA

JAVA
/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2023 Apryse Group NV
    Authors: Apryse Software.

    For more information, please contact iText Software at this address:
    sales@itextpdf.com
 */
package com.itextpdf.samples.sandbox.tables;

import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.properties.BorderCollapsePropertyValue;
import com.itextpdf.layout.properties.BorderRadius;
import com.itextpdf.layout.properties.UnitValue;

import java.io.File;

public class RoundedCorners {
    public static final String DEST = "./target/sandbox/tables/rounded_corners.pdf";

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

        new RoundedCorners().manipulatePdf(DEST);
    }

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

        Table table = new Table(UnitValue.createPercentArray(3)).useAllAvailableWidth();

        // By default iText collapses borders and draws them on table level.
        // In this sample, however, we want each cell to draw its borders separately,
        // that's why we need to override border collapse.
        table.setBorderCollapse(BorderCollapsePropertyValue.SEPARATE);

        // Sets horizontal spacing between all the table's cells. See css's border-spacing for more information.
        table.setHorizontalBorderSpacing(5);

        Cell cell = getCell("These cells have rounded borders at the top.");
        table.addCell(cell);

        cell = getCell("These cells aren't rounded at the bottom.");
        table.addCell(cell);

        cell = getCell("A custom cell event was used to achieve this.");
        table.addCell(cell);

        doc.add(table);

        doc.close();
    }

    private static Cell getCell(String content) {
        Cell cell = new Cell().add(new Paragraph(content));
        cell.setBorderTopRightRadius(new BorderRadius(4));
        cell.setBorderTopLeftRadius(new BorderRadius(4));
        return cell;
    }
}

C#

C#
using System;
using System.IO;
using iText.Kernel.Pdf;
using iText.Layout;
using iText.Layout.Element;
using iText.Layout.Properties;

namespace iText.Samples.Sandbox.Tables
{
    public class RoundedCorners
    {
        public static readonly string DEST = "results/sandbox/tables/rounded_corners.pdf";

        public static void Main(String[] args)
        {
            FileInfo file = new FileInfo(DEST);
            file.Directory.Create();

            new RoundedCorners().ManipulatePdf(DEST);
        }

        private void ManipulatePdf(String dest)
        {
            PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
            Document doc = new Document(pdfDoc);

            Table table = new Table(UnitValue.CreatePercentArray(3)).UseAllAvailableWidth();

            // By default iText collapses borders and draws them on table level.
            // In this sample, however, we want each cell to draw its borders separately,
            // that's why we need to override border collapse.
            table.SetBorderCollapse(BorderCollapsePropertyValue.SEPARATE);

            // Sets horizontal spacing between all the table's cells. See css's border-spacing for more information.
            table.SetHorizontalBorderSpacing(5);

            Cell cell = GetCell("These cells have rounded borders at the top.");
            table.AddCell(cell);

            cell = GetCell("These cells aren't rounded at the bottom.");
            table.AddCell(cell);

            cell = GetCell("A custom cell event was used to achieve this.");
            table.AddCell(cell);

            doc.Add(table);

            doc.Close();
        }

        private static Cell GetCell(String content)
        {
            Cell cell = new Cell().Add(new Paragraph(content));
            cell.SetBorderTopRightRadius(new BorderRadius(4));
            cell.SetBorderTopLeftRadius(new BorderRadius(4));
            return cell;
        }
    }
}

tableborder

JAVA

JAVA
/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2023 Apryse Group NV
    Authors: Apryse Software.

    For more information, please contact iText Software at this address:
    sales@itextpdf.com
 */
package com.itextpdf.samples.sandbox.tables;

import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.borders.Border;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.properties.UnitValue;
import com.itextpdf.layout.renderer.DrawContext;
import com.itextpdf.layout.renderer.IRenderer;
import com.itextpdf.layout.renderer.TableRenderer;

import java.io.File;

public class TableBorder {
    public static final String DEST = "./target/sandbox/tables/tables_border.pdf";

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

        new TableBorder().manipulatePdf(DEST);
    }

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

        Table table = new Table(UnitValue.createPercentArray(4)).useAllAvailableWidth();

        for (int aw = 0; aw < 16; aw++) {
            table.addCell(new Cell().add(new Paragraph("hi")).setBorder(Border.NO_BORDER));
        }

        // Notice that one should set renderer after cells are added to the table
        table.setNextRenderer(new TableBorderRenderer(table));

        doc.add(table);

        doc.close();
    }


    private static class TableBorderRenderer extends TableRenderer {
        public TableBorderRenderer(Table modelElement) {
            super(modelElement);
        }

        // If a renderer overflows on the next area, iText uses #getNextRenderer() method to create a new renderer for the overflow part.
        // If #getNextRenderer() isn't overridden, the default method will be used and thus the default rather than the custom
        // renderer will be created
        @Override
        public IRenderer getNextRenderer() {
            return new TableBorderRenderer((Table) modelElement);
        }

        @Override
        protected void drawBorders(DrawContext drawContext) {
            Rectangle rect = getOccupiedAreaBBox();
            drawContext.getCanvas()
                    .saveState()
                    .rectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight())
                    .stroke()
                    .restoreState();
        }
    }
}

C#

C#
using System;
using System.IO;
using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.Layout;
using iText.Layout.Borders;
using iText.Layout.Element;
using iText.Layout.Properties;
using iText.Layout.Renderer;

namespace iText.Samples.Sandbox.Tables
{
    public class TableBorder
    {
        public static readonly string DEST = "results/sandbox/tables/tables_border.pdf";

        public static void Main(String[] args)
        {
            FileInfo file = new FileInfo(DEST);
            file.Directory.Create();

            new TableBorder().ManipulatePdf(DEST);
        }

        private void ManipulatePdf(String dest)
        {
            PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
            Document doc = new Document(pdfDoc);

            Table table = new Table(UnitValue.CreatePercentArray(4)).UseAllAvailableWidth();

            for (int aw = 0; aw < 16; aw++)
            {
                table.AddCell(new Cell().Add(new Paragraph("hi")).SetBorder(Border.NO_BORDER));
            }

            // Notice that one should set renderer after cells are added to the table
            table.SetNextRenderer(new TableBorderRenderer(table));

            doc.Add(table);

            doc.Close();
        }

        private class TableBorderRenderer : TableRenderer
        {
            public TableBorderRenderer(Table modelElement)
                : base(modelElement)
            {
            }            
            
            // If renderer overflows on the next area, iText uses getNextRender() method to create a renderer for the overflow part.
            // If getNextRenderer isn't overriden, the default method will be used and thus a default rather than custom
            // renderer will be created
            public override IRenderer GetNextRenderer()
            {
                return new TableBorderRenderer((Table) modelElement);
            }

            protected override void DrawBorders(DrawContext drawContext)
            {
                Rectangle rect = GetOccupiedAreaBBox();
                drawContext.GetCanvas()
                    .SaveState()
                    .Rectangle(rect.GetLeft(), rect.GetBottom(), rect.GetWidth(), rect.GetHeight())
                    .Stroke()
                    .RestoreState();
            }
        }
    }
}
JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.