3

When generating a report with JasperReports, I am seeing weird borders drawn by the reporting engine. Sometimes the borders are drawn so that the lines jut out at 100% zoom level, as seen in this image. This means that when I attempt to draw a square textbox, it will not look like a square unless I zoom in to 150% for more. Here is an image showing the issue.

Bad Table

Am I doing something wrong when setting the border settings in iReport? How can I fix this problem?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
futureelite7
  • 11,462
  • 10
  • 53
  • 87
  • 1
    Do you see the same artifact in the generated reports (as *pdf*, for example)? – Alex K Aug 08 '13 at 07:21
  • 1
    Yes, this image is captured from the output pdf. – futureelite7 Aug 08 '13 at 08:43
  • 1
    I've just tried to open the generated *pdf* file in *Foxit Reader* - everything is ok. In *Adobe Reader* I can see the effect you talking about. May be it is a "feature" of your viewer (*Adobe Reader*?). – Alex K Aug 08 '13 at 09:47

1 Answers1

3

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;
      }
    }
  }
}