/**
  * Draw vertical separator lines between cells.
  *
  * @throws IOException
  */
 private void drawVerticalLines(PDPageContentStream pageStream, int columnCount, float crtLineY)
     throws IOException {
   pageStream.addLine(MARGIN, tableTopY, MARGIN, crtLineY);
   for (int i = 0; i < columnCount; i++) {
     float crtY = MARGIN + rightEdgePos[i];
     pageStream.addLine(crtY, tableTopY, crtY, crtLineY);
   }
   pageStream.closeAndStroke();
 }
 private PDPageContentStream addPageToDoc(PDDocument doc) throws IOException {
   PDPage page = new PDPage();
   page.setMediaBox(PAGE_SIZE);
   page.setRotation(IS_LANDSCAPE ? 90 : 0);
   doc.addPage(page);
   PDPageContentStream contentStream = new PDPageContentStream(doc, page, false, false);
   // User transformation matrix to change the reference when drawing.
   // This is necessary for the landscape position to draw correctly
   if (IS_LANDSCAPE) {
     contentStream.transform(new Matrix(0f, 1f, -1f, 0f, PAGE_SIZE.getWidth(), 0f));
   }
   contentStream.setFont(TEXT_FONT, FONT_SIZE);
   contentStream.setLineWidth(0.25f);
   return contentStream;
 }
  public ByteArrayOutputStream generateReport(
      final int lineCount, final int columnCount, List<String[]> tableContent) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    // Calculate center alignment for text in cell considering font height
    final float startTextY = tableTopY - (ROW_HEIGHT / 2) - (TEXT_LINE_HEIGHT / 4);
    PDDocument doc = new PDDocument();
    PDPageContentStream pageStream = addPageToDoc(doc);
    float crtLineY = tableTopY;
    float crtTextY = startTextY;
    // draw the header
    float rowHeight = drawTableHeader(pageStream, columnCount, crtTextY, crtLineY);
    crtLineY -= rowHeight;
    crtTextY -= rowHeight;

    // draw the lines
    for (String[] crtLine : tableContent) {
      if (crtTextY <= MARGIN) {
        drawVerticalLines(pageStream, columnCount, crtLineY);
        pageStream.close();
        // start new Page
        pageStream = addPageToDoc(doc);
        crtLineY = tableTopY;
        crtTextY = startTextY;
        rowHeight = drawTableHeader(pageStream, columnCount, crtTextY, crtLineY);
        crtLineY -= rowHeight;
        crtTextY -= rowHeight;
      }
      rowHeight = drawTableRow(pageStream, crtLine, columnCount, crtTextY, crtLineY);
      crtLineY -= rowHeight;
      crtTextY -= rowHeight;
    }

    drawVerticalLines(pageStream, columnCount, crtLineY);
    pageStream.close();
    doc.save(baos);
    doc.close();
    return baos;
  }
  /**
   * Draw the Table header.
   *
   * @param crtLineY
   * @return
   */
  private float drawTableHeader(
      PDPageContentStream pageStream, int columnCount, float startTextY, float crtLineY)
      throws IOException {
    // Position cursor to start drawing content
    float crtX = MARGIN + CELL_MARGIN;
    float maxHeight = 0;

    pageStream.drawLine(MARGIN, crtLineY, MARGIN + rightEdgePos[columnCount - 1], crtLineY);

    for (int i = 0; i < columnCount; i++) {
      String text = columns[i].getName();
      float cellHeight = writeCellContent(i, text, pageStream, crtX, startTextY);
      if (cellHeight > maxHeight) {
        maxHeight = cellHeight;
      }
      crtX += columns[i].getWidth();
    }

    pageStream.drawLine(
        MARGIN, crtLineY - maxHeight, MARGIN + rightEdgePos[columnCount - 1], crtLineY - maxHeight);
    return maxHeight;
  }
  /**
   * Draw the Line content.
   *
   * @param crtLineY
   * @return
   */
  private float drawTableRow(
      PDPageContentStream pageStream,
      String[] lineContent,
      int columnCount,
      float startTextY,
      float crtLineY)
      throws IOException {
    float crtX = MARGIN + CELL_MARGIN;
    float maxHeight = 0;

    for (int i = 0; i < columnCount; i++) {
      float cellHeight = writeCellContent(i, lineContent[i], pageStream, crtX, startTextY);
      if (cellHeight > maxHeight) {
        maxHeight = cellHeight;
      }
      crtX += columns[i].getWidth();
    }
    pageStream.drawLine(
        MARGIN, crtLineY - maxHeight, MARGIN + rightEdgePos[columnCount - 1], crtLineY - maxHeight);
    return maxHeight;
  }
  @SuppressWarnings("deprecation")
  @Override
  public void generateReport() throws ReportException {
    int pageNum = 1;
    // log.info("Report creation started");
    // declare data variables
    List<WarehousePart> records = null;
    try {
      records = gateway.fetchWarehouseAndParts();
    } catch (GatewayException e) {
      throw new ReportException("Error in report generation: " + e.getMessage());
    }

    // prep the report page 1
    doc = new PDDocument();
    PDPage page1 = new PDPage();
    PDRectangle rect = page1.getMediaBox();
    doc.addPage(page1);

    PDPage page2 = new PDPage();
    PDRectangle rect2 = page2.getMediaBox();
    doc.addPage(page2);
    // get content stream for page 1
    PDPageContentStream content = null;

    PDFont fontPlain = PDType1Font.HELVETICA;
    PDFont fontBold = PDType1Font.HELVETICA_BOLD;
    PDFont fontItalic = PDType1Font.HELVETICA_OBLIQUE;
    PDFont fontMono = PDType1Font.COURIER;
    PDFont fontMonoBold = PDType1Font.COURIER_BOLD;

    page1.setRotation(90);

    float margin = 20;

    try {
      content = new PDPageContentStream(doc, page1);

      // print header of page 1
      content.concatenate2CTM(0, 1, -1, 0, 718, 0);
      content.setNonStrokingColor(Color.CYAN);
      content.setStrokingColor(Color.BLACK);

      float bottomY = rect.getHeight() - margin - 100;
      float headerEndX = rect.getWidth() - margin * 2;

      content.addRect(margin, bottomY, headerEndX, 100);
      content.fillAndStroke();

      content.setNonStrokingColor(Color.BLACK);

      // print report title
      content.setFont(fontBold, 24);
      content.beginText();
      content.newLineAtOffset(margin + 15, bottomY + 15);
      content.showText("Warehouse Inventory Summary");
      content.endText();

      // page Number
      content.setFont(fontBold, 12);
      content.beginText();
      content.newLineAtOffset(710, 150);
      content.showText("Page " + pageNum);
      content.endText();

      // Date
      Date date = new Date();
      content.setFont(fontBold, 12);
      content.beginText();
      content.newLineAtOffset(30, 150);
      content.showText(date.toString());
      content.endText();

      content.setFont(fontMonoBold, 12);
      float dataY = 610;

      // colum layout, this might require tweaking
      // warehouse Name    Part #     Part Name            Quantity      Unit
      float colX_0 = margin + 15; // warehouse name
      float colX_1 = colX_0 + 180; // Part number
      float colX_2 = colX_1 + 100; // Part Name
      float colX_3 = colX_2 + 180; // quantity
      float colX_4 = colX_3 + 100; // unit

      // print the colum texts
      content.beginText();
      content.newLineAtOffset(colX_0, dataY);
      content.showText("Warehouse Name");
      content.endText();
      content.beginText();
      content.newLineAtOffset(colX_1, dataY);
      content.showText("Part Number");
      content.endText();
      content.beginText();
      content.newLineAtOffset(colX_2, dataY);
      content.showText("Part Name");
      content.endText();
      content.beginText();
      content.newLineAtOffset(colX_3, dataY);
      content.showText("Quantity");
      content.endText();
      content.beginText();
      content.newLineAtOffset(colX_4, dataY);
      content.showText("Unit");
      content.endText();

      // print the report rows
      content.setFont(fontMono, 12);

      int counter = 1;

      for (WarehousePart wp : records) {
        // the offset for the current row
        float offset = dataY - (counter * (fontMono.getHeight(12) + 15));

        Warehouse w = wp.getOwner();
        Part p = wp.getPart();

        content.beginText();
        content.newLineAtOffset(colX_0, offset);
        content.showText(w.getWarehouseName());
        content.endText();
        content.beginText();
        content.newLineAtOffset(colX_1, offset);
        content.showText("" + p.getPartNumber());
        content.endText();
        content.beginText();
        content.newLineAtOffset(colX_2, offset);
        content.showText("" + p.getPartName());
        content.endText();
        content.beginText();
        content.newLineAtOffset(colX_3, offset);
        content.showText("" + wp.getQuantity());
        content.endText();
        content.beginText();
        content.newLineAtOffset(colX_4, offset);
        content.showText("" + p.getUnitQuanitity());
        content.endText();

        counter++;
        if (counter > 25) {
          content.close();
          break;
        }
      }

      content = new PDPageContentStream(doc, page2);
      content.concatenate2CTM(0, 1, -1, 0, 718, 0);
      content.setFont(fontMono, 12);
      page2.setRotation(90);
      pageNum = 2;
      int counter2 = 1;

      // page Number
      content.setFont(fontBold, 12);
      content.beginText();
      content.newLineAtOffset(710, 150);
      content.showText("Page " + pageNum);
      content.endText();

      // Date

      content.setFont(fontBold, 12);
      content.beginText();
      content.newLineAtOffset(30, 150);
      content.showText(date.toString());
      content.endText();

      content.setFont(fontMono, 12);

      for (WarehousePart wp : records.subList(25, records.size())) {
        // the offset for the current row
        float offset = dataY - (counter2 * (fontMono.getHeight(12) + 15));

        Warehouse w = wp.getOwner();
        Part p = wp.getPart();

        content.beginText();
        content.newLineAtOffset(colX_0, offset);
        content.showText(w.getWarehouseName());
        content.endText();
        content.beginText();
        content.newLineAtOffset(colX_1, offset);
        content.showText("" + p.getPartNumber());
        content.endText();
        content.beginText();
        content.newLineAtOffset(colX_2, offset);
        content.showText("" + p.getPartName());
        content.endText();
        content.beginText();
        content.newLineAtOffset(colX_3, offset);
        content.showText("" + wp.getQuantity());
        content.endText();
        content.beginText();
        content.newLineAtOffset(colX_4, offset);
        content.showText("" + p.getUnitQuanitity());
        content.endText();

        counter2++;
      }

    } catch (IOException e) {
      throw new ReportException("Error in report generation: " + e.getMessage());
    } finally {

      try {
        content.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
  private float writeCellContent(
      int colIndex, String text, PDPageContentStream pageStream, float crtX, float startTextY)
      throws IOException {
    float cellHeight = ROW_HEIGHT;
    float cellInnerWidth = columns[colIndex].getWidth() - 2 * CELL_MARGIN;
    float textWidth = TEXT_FONT.getStringWidth(text) / 1000 * FONT_SIZE;
    if (textWidth <= cellInnerWidth) { // line fits in cell
      pageStream.beginText();
      if (columns[colIndex].getAlignment() == ColumnAlignment.LEFT) {
        pageStream.newLineAtOffset(crtX, startTextY);
      } else {
        float textLeftPos = crtX + cellInnerWidth - textWidth;
        pageStream.newLineAtOffset(textLeftPos, startTextY);
      }
      pageStream.showText(text != null ? text : "");
      pageStream.endText();
    } else { // line must be wrapped

      int start = 0;
      int end = 0;
      float crtCellY = startTextY;
      String currentToken = "";
      for (int i : possibleWrapPoints(text)) {
        float width = TEXT_FONT.getStringWidth(text.substring(start, i)) / 1000 * FONT_SIZE;
        if (start < end && width > cellInnerWidth) {
          currentToken = text.substring(start, end);
          // Draw partial text and increase height
          pageStream.beginText();
          if (columns[colIndex].getAlignment() == ColumnAlignment.LEFT) {
            pageStream.newLineAtOffset(crtX, crtCellY);
          } else {
            float currentTokenWidth = TEXT_FONT.getStringWidth(currentToken) / 1000 * FONT_SIZE;
            float textLeftPos = crtX + cellInnerWidth - currentTokenWidth;
            pageStream.newLineAtOffset(textLeftPos, crtCellY);
          }
          pageStream.showText(currentToken);
          pageStream.endText();
          crtCellY -= TEXT_LINE_HEIGHT;
          start = end;
          cellHeight += TEXT_LINE_HEIGHT;
        }
        end = i;
      }
      // Last piece of text
      pageStream.beginText();
      currentToken = text.substring(start);
      if (columns[colIndex].getAlignment() == ColumnAlignment.LEFT) {
        pageStream.newLineAtOffset(crtX, crtCellY);
      } else {
        float currentTokenWidth = TEXT_FONT.getStringWidth(currentToken) / 1000 * FONT_SIZE;
        float textLeftPos = crtX + cellInnerWidth - currentTokenWidth;
        pageStream.newLineAtOffset(textLeftPos, crtCellY);
      }
      pageStream.showText(currentToken);
      pageStream.endText();
    }

    return cellHeight;
  }