I had the same problem. When exporting tables into PDF and using thin table borders, lines of cell borders seem to overstrike borders of the table by one pixel. Visual artifacts are visible only in some PDF viewers (they are visible in the Adobe Reader) and only at some zoom levels. Appearance of artifacts changes when line art smoothing is turned on or off in PDF viewer. If you zoom on overstrike at maximal level, you see that lines are drawn correctly - the artifacts are made by the PDF viewer when scaling the document.
When checking the JasperReports code, I've found that PDF export draws table border as four single lines. They are drawn with no caps (the line is cut off immediately at the end). When drawing a square border that way, the corners would not connect completely, so each line is drawn slightly longer than the border to compensate for the missing cap. The algorithm for this seems correct to me, however there is something going on in the Adobe Reader, some rounding and image "improvements" which cause image artifacts to appear.
I was able to come up with a solution which fixes this issue in my case. I have subclassed JRPdfExporter and modified the code which draws borders. Usage is simple, just use my class instead of JRPdfExporter. I use JasperReports 5.0.0.
package cz.jwa.jasper;
import java.awt.Color;
import net.sf.jasperreports.engine.JRLineBox;
import net.sf.jasperreports.engine.JRPen;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.export.JRPdfExporter;
import net.sf.jasperreports.engine.export.legacy.BorderOffset;
import net.sf.jasperreports.engine.type.LineStyleEnum;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PdfContentByte;
public class JWAPdfExporter extends JRPdfExporter
{
protected void exportBox(JRLineBox box, JRPrintElement element)
{
if(!isBoxVisible(box)) return;
pdfContentByte.setLineCap(PdfContentByte.LINE_CAP_PROJECTING_SQUARE);
float x1 = element.getX() + getOffsetX();
float y1 = jasperPrint.getPageHeight() - element.getY() - getOffsetY();
float x2 = element.getX() + getOffsetX() + element.getWidth();
float y2 = jasperPrint.getPageHeight() - element.getY() - getOffsetY() - element.getHeight();
Rectangle r = new Rectangle(x1, y2, x2, y1);
int borderFlag = 0;
boolean same = true;
float lineWidth = 0;
Color lineColor = null;
if(box.getLeftPen().getLineWidth().floatValue() > 0f)
{
borderFlag |= Rectangle.LEFT;
lineWidth = box.getLeftPen().getLineWidth().floatValue();
lineColor = box.getLeftPen().getLineColor();
}
if(box.getTopPen().getLineWidth().floatValue() > 0f)
{
borderFlag |= Rectangle.TOP;
if(lineWidth == 0)
{
lineWidth = box.getTopPen().getLineWidth().floatValue();
lineColor = box.getTopPen().getLineColor();
}
else if(lineWidth != box.getTopPen().getLineWidth().floatValue()
|| !lineColor.equals(box.getTopPen().getLineColor()))
{
same = false;
}
}
if(box.getRightPen().getLineWidth().floatValue() > 0f)
{
borderFlag |= Rectangle.RIGHT;
if(lineWidth == 0)
{
lineWidth = box.getRightPen().getLineWidth().floatValue();
lineColor = box.getRightPen().getLineColor();
}
else if(lineWidth != box.getRightPen().getLineWidth().floatValue()
|| !lineColor.equals(box.getRightPen().getLineColor()))
{
same = false;
}
}
if(box.getBottomPen().getLineWidth().floatValue() > 0f)
{
borderFlag |= Rectangle.BOTTOM;
if(lineWidth == 0)
{
lineWidth = box.getBottomPen().getLineWidth().floatValue();
lineColor = box.getBottomPen().getLineColor();
}
else if(lineWidth != box.getBottomPen().getLineWidth().floatValue()
|| !lineColor.equals(box.getBottomPen().getLineColor()))
{
same = false;
}
}
if(same)
{
r.setBorder(borderFlag);
r.setBorderColor(lineColor);
r.setBorderWidth(lineWidth);
pdfContentByte.rectangle(r);
pdfContentByte.stroke();
pdfContentByte.setLineDash(0.0F);
pdfContentByte.setLineCap(PdfContentByte.LINE_CAP_PROJECTING_SQUARE);
}
else
{
super.exportBox(box, element);
}
}
private boolean isBoxVisible(JRLineBox box)
{
return box.getLeftPen().getLineWidth().floatValue() > 0f
|| box.getTopPen().getLineWidth().floatValue() > 0f
|| box.getRightPen().getLineWidth().floatValue() > 0f
|| box.getBottomPen().getLineWidth().floatValue() > 0f
;
}
protected void exportTopPen(
JRPen topPen,
JRPen leftPen,
JRPen rightPen,
JRPrintElement element)
{
if (topPen.getLineWidth().floatValue() > 0f)
{
float leftOffset = 0; //leftPen.getLineWidth().floatValue() / 2 - BorderOffset.getOffset(leftPen);
float rightOffset = 0; //rightPen.getLineWidth().floatValue() / 2 - BorderOffset.getOffset(rightPen);
preparePen(pdfContentByte, topPen, PdfContentByte.LINE_CAP_BUTT);
if (topPen.getLineStyleValue() == LineStyleEnum.DOUBLE)
{
float topOffset = topPen.getLineWidth().floatValue();
pdfContentByte.moveTo(
element.getX() + getOffsetX() - leftOffset,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() + topOffset / 3
);
pdfContentByte.lineTo(
element.getX() + getOffsetX() + element.getWidth() + rightOffset,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() + topOffset / 3
);
pdfContentByte.stroke();
pdfContentByte.moveTo(
element.getX() + getOffsetX() + leftOffset / 3,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - topOffset / 3
);
pdfContentByte.lineTo(
element.getX() + getOffsetX() + element.getWidth() - rightOffset / 3,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - topOffset / 3
);
pdfContentByte.stroke();
}
else
{
float topOffset = BorderOffset.getOffset(topPen);
pdfContentByte.moveTo(
element.getX() + getOffsetX() - leftOffset,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - topOffset
);
pdfContentByte.lineTo(
element.getX() + getOffsetX() + element.getWidth() + rightOffset,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - topOffset
);
pdfContentByte.stroke();
}
}
}
protected void exportLeftPen(JRPen topPen, JRPen leftPen, JRPen bottomPen, JRPrintElement element)
{
if (leftPen.getLineWidth().floatValue() > 0f)
{
float topOffset = 0; //topPen.getLineWidth().floatValue() / 2 - BorderOffset.getOffset(topPen);
float bottomOffset = 0; //bottomPen.getLineWidth().floatValue() / 2 - BorderOffset.getOffset(bottomPen);
preparePen(pdfContentByte, leftPen, PdfContentByte.LINE_CAP_BUTT);
if (leftPen.getLineStyleValue() == LineStyleEnum.DOUBLE)
{
float leftOffset = leftPen.getLineWidth().floatValue();
pdfContentByte.moveTo(
element.getX() + getOffsetX() - leftOffset / 3,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() + topOffset
);
pdfContentByte.lineTo(
element.getX() + getOffsetX() - leftOffset / 3,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - element.getHeight() - bottomOffset
);
pdfContentByte.stroke();
pdfContentByte.moveTo(
element.getX() + getOffsetX() + leftOffset / 3,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - topOffset / 3
);
pdfContentByte.lineTo(
element.getX() + getOffsetX() + leftOffset / 3,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - element.getHeight() + bottomOffset / 3
);
pdfContentByte.stroke();
}
else
{
float leftOffset = BorderOffset.getOffset(leftPen);
pdfContentByte.moveTo(
element.getX() + getOffsetX() + leftOffset,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() + topOffset
);
pdfContentByte.lineTo(
element.getX() + getOffsetX() + leftOffset,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - element.getHeight() - bottomOffset
);
pdfContentByte.stroke();
}
}
}
/**
*
*/
protected void exportBottomPen(JRPen leftPen, JRPen bottomPen, JRPen rightPen, JRPrintElement element)
{
if (bottomPen.getLineWidth().floatValue() > 0f)
{
float leftOffset = 0; //leftPen.getLineWidth().floatValue() / 2 - BorderOffset.getOffset(leftPen);
float rightOffset = 0; //rightPen.getLineWidth().floatValue() / 2 - BorderOffset.getOffset(rightPen);
preparePen(pdfContentByte, bottomPen, PdfContentByte.LINE_CAP_BUTT);
if (bottomPen.getLineStyleValue() == LineStyleEnum.DOUBLE)
{
float bottomOffset = bottomPen.getLineWidth().floatValue();
pdfContentByte.moveTo(
element.getX() + getOffsetX() - leftOffset,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - element.getHeight() - bottomOffset / 3
);
pdfContentByte.lineTo(
element.getX() + getOffsetX() + element.getWidth() + rightOffset,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - element.getHeight() - bottomOffset / 3
);
pdfContentByte.stroke();
pdfContentByte.moveTo(
element.getX() + getOffsetX() + leftOffset / 3,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - element.getHeight() + bottomOffset / 3
);
pdfContentByte.lineTo(
element.getX() + getOffsetX() + element.getWidth() - rightOffset / 3,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - element.getHeight() + bottomOffset / 3
);
pdfContentByte.stroke();
}
else
{
float bottomOffset = BorderOffset.getOffset(bottomPen);
pdfContentByte.moveTo(
element.getX() + getOffsetX() - leftOffset,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - element.getHeight() + bottomOffset
);
pdfContentByte.lineTo(
element.getX() + getOffsetX() + element.getWidth() + rightOffset,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - element.getHeight() + bottomOffset
);
pdfContentByte.stroke();
}
}
}
/**
*
*/
protected void exportRightPen(JRPen topPen, JRPen bottomPen, JRPen rightPen, JRPrintElement element)
{
if (rightPen.getLineWidth().floatValue() > 0f)
{
float topOffset = 0; //topPen.getLineWidth().floatValue() / 2 - BorderOffset.getOffset(topPen);
float bottomOffset = 0; //bottomPen.getLineWidth().floatValue() / 2 - BorderOffset.getOffset(bottomPen);
preparePen(pdfContentByte, rightPen, PdfContentByte.LINE_CAP_BUTT);
if (rightPen.getLineStyleValue() == LineStyleEnum.DOUBLE)
{
float rightOffset = rightPen.getLineWidth().floatValue();
pdfContentByte.moveTo(
element.getX() + getOffsetX() + element.getWidth() + rightOffset / 3,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() + topOffset
);
pdfContentByte.lineTo(
element.getX() + getOffsetX() + element.getWidth() + rightOffset / 3,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - element.getHeight() - bottomOffset
);
pdfContentByte.stroke();
pdfContentByte.moveTo(
element.getX() + getOffsetX() + element.getWidth() - rightOffset / 3,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - topOffset / 3
);
pdfContentByte.lineTo(
element.getX() + getOffsetX() + element.getWidth() - rightOffset / 3,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - element.getHeight() + bottomOffset / 3
);
pdfContentByte.stroke();
}
else
{
float rightOffset = BorderOffset.getOffset(rightPen);
pdfContentByte.moveTo(
element.getX() + getOffsetX() + element.getWidth() - rightOffset,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() + topOffset
);
pdfContentByte.lineTo(
element.getX() + getOffsetX() + element.getWidth() - rightOffset,
jasperPrint.getPageHeight() - element.getY() - getOffsetY() - element.getHeight() - bottomOffset
);
pdfContentByte.stroke();
}
}
}
private static void preparePen(PdfContentByte pdfContentByte, JRPen pen, int lineCap)
{
float lineWidth = pen.getLineWidth().floatValue();
if (lineWidth <= 0)
{
return;
}
pdfContentByte.setLineWidth(lineWidth);
pdfContentByte.setLineCap(lineCap);
Color color = pen.getLineColor();
pdfContentByte.setRGBColorStroke(color.getRed(), color.getGreen(), color.getBlue());
switch (pen.getLineStyleValue())
{
case DOUBLE:
{
pdfContentByte.setLineWidth(lineWidth / 3);
pdfContentByte.setLineDash(0f);
break;
}
case DOTTED:
{
switch (lineCap)
{
case PdfContentByte.LINE_CAP_BUTT:
{
pdfContentByte.setLineDash(lineWidth, lineWidth, 0f);
break;
}
case PdfContentByte.LINE_CAP_PROJECTING_SQUARE:
{
pdfContentByte.setLineDash(0, 2 * lineWidth, 0f);
break;
}
}
break;
}
case DASHED:
{
switch (lineCap)
{
case PdfContentByte.LINE_CAP_BUTT:
{
pdfContentByte.setLineDash(5 * lineWidth, 3 * lineWidth, 0f);
break;
}
case PdfContentByte.LINE_CAP_PROJECTING_SQUARE:
{
pdfContentByte.setLineDash(4 * lineWidth, 4 * lineWidth, 0f);
break;
}
}
break;
}
case SOLID:
default:
{
pdfContentByte.setLineDash(0f);
break;
}
}
}
}