How to define different border types for a single cell?

Tags: tablesdotted linecell eventcell borderiText 5

When I try to create a dotted cell I am using a PdfPCellEvent. However I get all of the borders drawn with dotted line. When I set up the cell.setBorder(Rectangle.BOTTOM | Rectangle.RIGHT); the bottom and right get drawn with solid line. When I remove the setBorder() I get all borders dotted.

  1. How can I draw a cell with three types of borders solid, dotted and NO_BORDER. I want to create a simple table as shown in the image below, where the blue lines stands for NO_BORDER.
  2. If two cells share one border, the dotted line overlaps and combines into solid line. Is the only way to avoid it is to remove the shared border form one of the cells?

Example table

Question posted on StackOverflow on Jan 1, 2016 by mCs

I think you are mixing up things.

If you use cell events to draw borders, you create custom borders, you should remove all automated borders. So you always need:

 cell.setBorder(PdfPCell.NO_BORDER);

If you want to draw partial borders, you need to draw partial borders.

When you add a border using a cell event, you add a number of lines using a sequence of moveTo(), lineTo() and stroke() commands. In other words: you decide which lines are drawn. If you want to reduce the number of borders drawn, you need to draw less lines.

Please take a look at the DottedLineCell2 example. This is a variation on the example you refer to. In this example, I create an event that draws each border separately:

class DottedCell implements PdfPCellEvent {
    private int border = 0;
    public DottedCell(int border) {
        this.border = border;
    }
    public void cellLayout(PdfPCell cell, Rectangle position,
        PdfContentByte[] canvases) {
        PdfContentByte canvas = canvases[PdfPTable.LINECANVAS];
        canvas.saveState();
        canvas.setLineDash(0, 4, 2);
        if ((border & PdfPCell.TOP) == PdfPCell.TOP) {
            canvas.moveTo(position.getRight(), position.getTop());
            canvas.lineTo(position.getLeft(), position.getTop());
        }
        if ((border & PdfPCell.BOTTOM) == PdfPCell.BOTTOM) {
            canvas.moveTo(position.getRight(), position.getBottom());
            canvas.lineTo(position.getLeft(), position.getBottom());
        }
        if ((border & PdfPCell.RIGHT) == PdfPCell.RIGHT) {
            canvas.moveTo(position.getRight(), position.getTop());
            canvas.lineTo(position.getRight(), position.getBottom());
        }
        if ((border & PdfPCell.LEFT) == PdfPCell.LEFT) {
            canvas.moveTo(position.getLeft(), position.getTop());
            canvas.lineTo(position.getLeft(), position.getBottom());
        }
        canvas.stroke();
        canvas.restoreState();
    }
}

When creating an instance of this event, you have to pass a border value. In the cellLayout() method, we'll look at this border value:

  • When the TOP bit is selected, we construct a line from the upper-right corner to the upper-left corner,

  • When the BOTTOM bit is selected, we construct a line from the lower-right corner to the lower-left corner,

  • When the RIGHT bit is selected, we construct a line from the upper-right corner to the lower-right corner,

  • When the LEFT bit is selected, we construct a line from the upper-left corner to the lower-left corner.

Once we've checked all the sides of the border, we stroke() the lines.

In the following example, I create two tables:

public void createPdf(String dest) throws IOException, DocumentException {
    Document document = new Document();
    PdfWriter.getInstance(document, new FileOutputStream(dest));
    document.open();
 
    PdfPTable table;
    PdfPCell cell;
 
    table = new PdfPTable(4);
    table.setSpacingAfter(30);
    cell = new PdfPCell(new Phrase("left border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new DottedCell(PdfPCell.LEFT));
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("right border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new DottedCell(PdfPCell.RIGHT));
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("top border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new DottedCell(PdfPCell.TOP));
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("bottom border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new DottedCell(PdfPCell.BOTTOM));
    table.addCell(cell);
    document.add(table);
 
    table = new PdfPTable(4);
    table.setSpacingAfter(30);
    cell = new PdfPCell(new Phrase("left and top border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new DottedCell(PdfPCell.LEFT | PdfPCell.TOP));
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("right and bottom border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new DottedCell(PdfPCell.RIGHT | PdfPCell.BOTTOM));
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("no border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("full border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new DottedCell(PdfPCell.BOX));
    table.addCell(cell);
    document.add(table);
    document.close();
}

This is what the two tables look like: dotted_line_cell2.pdf

Table cells with different border types
Table cells with different border types

Update:

In your comment you claim that my answer isn't sufficient, explaining that your programming skills are rather limited: you can't adapt the cell event into an event that draws different types of borders. With respect to my initial example, you also ask "is it the only way?"

Of course it's not the only way: there are many different ways to achieve the result you desire. Allow me to present two extra examples (although there are many more possible variations, some of which might involve table events instead of cell events):

Extra example #1: Using an interface to define the line dash:

In the CustomBorder3 example, I copy/pasted the cell event from my previous example and I adapted it like this:

class CustomBorder implements PdfPCellEvent {
    protected LineDash left;
    protected LineDash right;
    protected LineDash top;
    protected LineDash bottom;
    public CustomBorder(LineDash left, LineDash right,
            LineDash top, LineDash bottom) {
        this.left = left;
        this.right = right;
        this.top = top;
        this.bottom = bottom;
    }
    public void cellLayout(PdfPCell cell, Rectangle position,
        PdfContentByte[] canvases) {
        PdfContentByte canvas = canvases[PdfPTable.LINECANVAS];
        if (top != null) {
            canvas.saveState();
            top.applyLineDash(canvas);
            canvas.moveTo(position.getRight(), position.getTop());
            canvas.lineTo(position.getLeft(), position.getTop());
            canvas.stroke();
            canvas.restoreState();
        }
        if (bottom != null) {
            canvas.saveState();
            bottom.applyLineDash(canvas);
            canvas.moveTo(position.getRight(), position.getBottom());
            canvas.lineTo(position.getLeft(), position.getBottom());
            canvas.stroke();
            canvas.restoreState();
        }
        if (right != null) {
            canvas.saveState();
            right.applyLineDash(canvas);
            canvas.moveTo(position.getRight(), position.getTop());
            canvas.lineTo(position.getRight(), position.getBottom());
            canvas.stroke();
            canvas.restoreState();
        }
        if (left != null) {
            canvas.saveState();
            left.applyLineDash(canvas);
            canvas.moveTo(position.getLeft(), position.getTop());
            canvas.lineTo(position.getLeft(), position.getBottom());
            canvas.stroke();
            canvas.restoreState();
        }
    }
}

As you can see, I no longer define a border value, but instead I define four values: left, right, top, and bottom. In the cellLayout() method, I draw a line for each of those values that is different from null.

The variables are of type LineDash. LineDash is an interface with a single method:

interface LineDash {
    public void applyLineDash(PdfContentByte canvas);
}

I created three implementations for this interface:

class SolidLine implements LineDash {
    public void applyLineDash(PdfContentByte canvas) { }
}
class DottedLine implements LineDash {
    public void applyLineDash(PdfContentByte canvas) {
        canvas.setLineCap(PdfContentByte.LINE_CAP_ROUND);
        canvas.setLineDash(0, 4, 2);
    }
}
class DashedLine implements LineDash {
    public void applyLineDash(PdfContentByte canvas) {
        canvas.setLineDash(3, 3);
    }
}

You can easily create new implementations such as:

class DashedLine2 implements LineDash {
    float unitsOn;
    float phase;
    public DashedLine2(float unitsOn, float phase) {
        this.unitsOn = unitsOn;
        this.phase = phase;
    }
    public void applyLineDash(PdfContentByte canvas) {
        canvas.setLineDash(unitsOn, phase);
    }
}

You could even introduce an implementation that changes the color and the width of the border.

I can now use the CustomBorder event like this:

public void createPdf(String dest) throws IOException, DocumentException {
    Document document = new Document();
    PdfWriter.getInstance(document, new FileOutputStream(dest));
    document.open();
 
    PdfPTable table;
    PdfPCell cell;
    LineDash solid = new SolidLine();
    LineDash dotted = new DottedLine();
    LineDash dashed = new DashedLine();
 
    table = new PdfPTable(4);
    table.setSpacingAfter(30);
    cell = new PdfPCell(new Phrase("dotted left border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new CustomBorder(dotted, null, null, null));
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("solid right border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new CustomBorder(null, solid, null, null));
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("dashed top border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new CustomBorder(null, null, dashed, null));
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("bottom border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new CustomBorder(null, null, null, solid));
    table.addCell(cell);
    document.add(table);
 
    table = new PdfPTable(4);
    table.setSpacingAfter(30);
    cell = new PdfPCell(new Phrase("dotted left and solid top border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new CustomBorder(dotted, null, solid, null));
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("dashed right and dashed bottom border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new CustomBorder(null, dashed, null, dashed));
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("no border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("full solid border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new CustomBorder(solid, solid, solid, solid));
    table.addCell(cell);
    document.add(table);
    document.close();
}

The result looks like this:

Table cells with different border types
Table cells with different border types

Extra example #2: Using an abstract class as the basis of the cell event:

In the CustomBorder4 example, I copy/pasted the cell event from my previous example and I adapted it like this:

abstract class CustomBorder implements PdfPCellEvent {
    private int border = 0;
    public CustomBorder(int border) {
        this.border = border;
    }
    public void cellLayout(PdfPCell cell, Rectangle position,
        PdfContentByte[] canvases) {
        PdfContentByte canvas = canvases[PdfPTable.LINECANVAS];
        canvas.saveState();
        setLineDash(canvas);
        if ((border & PdfPCell.TOP) == PdfPCell.TOP) {
            canvas.moveTo(position.getRight(), position.getTop());
            canvas.lineTo(position.getLeft(), position.getTop());
        }
        if ((border & PdfPCell.BOTTOM) == PdfPCell.BOTTOM) {
            canvas.moveTo(position.getRight(), position.getBottom());
            canvas.lineTo(position.getLeft(), position.getBottom());
        }
        if ((border & PdfPCell.RIGHT) == PdfPCell.RIGHT) {
            canvas.moveTo(position.getRight(), position.getTop());
            canvas.lineTo(position.getRight(), position.getBottom());
        }
        if ((border & PdfPCell.LEFT) == PdfPCell.LEFT) {
            canvas.moveTo(position.getLeft(), position.getTop());
            canvas.lineTo(position.getLeft(), position.getBottom());
        }
        canvas.stroke();
        canvas.restoreState();
    }
 
    public abstract void setLineDash(PdfContentByte canvas);
}

This class is abstract, because it contains a method that isn't implemented. I can now extend this abstract class like this:

class SolidBorder extends CustomBorder {
    public SolidBorder(int border) { super(border); }
    public void setLineDash(PdfContentByte canvas) {}
}
class DottedBorder extends CustomBorder {
    public DottedBorder(int border) { super(border); }
    public void setLineDash(PdfContentByte canvas) {
        canvas.setLineCap(PdfContentByte.LINE_CAP_ROUND);
        canvas.setLineDash(0, 4, 2);
    }
}
class DashedBorder extends CustomBorder {
    public DashedBorder(int border) { super(border); }
    public void setLineDash(PdfContentByte canvas) {
        canvas.setLineDash(3, 3);
    }
}

Again, I could introduce different parameters to change the dash pattern, color, line width, etc. But that's something you can easily do yourself.

I have now three different cell events that I can use on different cells or on the same cell:

public void createPdf(String dest) throws IOException, DocumentException {
    Document document = new Document();
    PdfWriter.getInstance(document, new FileOutputStream(dest));
    document.open();
 
    PdfPTable table;
    PdfPCell cell;
 
    table = new PdfPTable(4);
    table.setSpacingAfter(30);
    cell = new PdfPCell(new Phrase("dotted left border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new DottedBorder(PdfPCell.LEFT));
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("solid right border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new SolidBorder(PdfPCell.RIGHT));
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("solid top border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new SolidBorder(PdfPCell.TOP));
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("dashed bottom border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new DashedBorder(PdfPCell.BOTTOM));
    table.addCell(cell);
    document.add(table);
 
    table = new PdfPTable(4);
    table.setSpacingAfter(30);
    cell = new PdfPCell(new Phrase("dotted left and dashed top border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new DottedBorder(PdfPCell.LEFT));
    cell.setCellEvent(new DashedBorder(PdfPCell.TOP));
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("solid right and dotted bottom border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new DottedBorder(PdfPCell.BOTTOM));
    cell.setCellEvent(new SolidBorder(PdfPCell.RIGHT));
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("no border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    table.addCell(cell);
    cell = new PdfPCell(new Phrase("full border"));
    cell.setBorder(PdfPCell.NO_BORDER);
    cell.setCellEvent(new DottedBorder(PdfPCell.LEFT | PdfPCell.RIGHT));
    cell.setCellEvent(new SolidBorder(PdfPCell.TOP));
    cell.setCellEvent(new DashedBorder(PdfPCell.BOTTOM));
    table.addCell(cell);
    document.add(table);
    document.close();
}

The result looks like this:

Table cells with different border types
Table cells with different border types

These are only two extra examples. One could easily write many more.