# Xpr2PdfWorker.java

    /*
    ** Module   : Xpr2PdfWorker.java
    ** Abstract : Class to generate PDF file from collected XPR objects.
    **
    ** Copyright (c) 2018-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------Description----------------------------------
    ** 001 EVL 20180516 Created initial version.
    ** 002 EVL 20180522 Adding multi-page support.  Images can use bottom right coort as starting
    **                  point.  Now supported.
    ** 003 EVL 20180527 Clean up and code optimization.  Make rectangle to be transparent.
    ** 004 EVL 20180531 Fixes for object's X coordinate calculation.  Adding filled rectangle support.
    ** 005 EVL 20180625 Changed logging level to disable message box for report compile and fill.
    ** 006 EVL 20181007 Adding ability to get image from application jar file.
    **     EVL 20181016 Fix for different font sizing in different OS.  Need to take into account
    **                  when computing bounding rectangle.
    ** 007 EVL 20190514 Changing source type to common interface to be use with different possible
    **                  PDF sources.  Typo error fix.
    ** 008 EVL 20190617 Adding line style attribute support.
    ** 009 EVL 20211026 Changing text font size type to float.
    **     EVL 20220519 For image consistency check we have to test image bytes size in addition to verify the
    **                  image byte pointer array is not null.
    ** 010 GBB 20240419 Ghost4j lib replaced by Apache pdfbox.
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.reporting;

    import com.goldencode.p2j.ui.client.*;
    import com.goldencode.p2j.ui.client.gui.driver.*;
    import com.goldencode.p2j.util.*;
    import net.sf.jasperreports.components.*;
    import net.sf.jasperreports.components.barbecue.*;
    import net.sf.jasperreports.engine.*;
    import net.sf.jasperreports.engine.component.*;
    import net.sf.jasperreports.engine.design.*;
    import net.sf.jasperreports.engine.export.*;
    import net.sf.jasperreports.engine.type.*;
    import net.sf.jasperreports.export.*;
    import org.apache.pdfbox.cos.*;
    import org.apache.pdfbox.pdmodel.*;
    import org.apache.pdfbox.pdmodel.graphics.*;

    import javax.imageio.*;
    import java.awt.image.BufferedImage;
    import java.awt.Color;
    import java.io.*;
    import java.util.*;

    /**
     * The object prepares the data structures required to build consistent Jasper report object and
     * initiates the process of generating output PDF file via Jasper API.
     */
    public class Xpr2PdfWorker
    {
       /** Font style normal. */
       public static final int FONT_STYLE_NORMAL = 0;

       /** Font style bold. */
       public static final int FONT_STYLE_BOLD = 1;

       /** Font style bold and italic. */
       public static final int FONT_STYLE_BOLD_ITALIC = 2;

       /** Font style italic. */
       public static final int FONT_STYLE_ITALIC = 3;

       /** Default DPI value for screen report. */
       public static final int DPI_DEFAULT = 72;

       /** The XPR base object to get all required info about objects to include in output. */
       private PdfProvider xprBaseInput = null;

       /** The output PDF file name to generate.  */
       private String pdfFileName = null;

       /** Parameters for Jasper export. */
       private Map<String, Object> jspParameters = new HashMap<String, Object>();

       /** Row modifier to convert XPR helper row coords to pixel. */
       private double pixPerRow = XprHelper.getRow2Pix(DPI_DEFAULT, XprEntity.LPI_DEFAULT);

       /** Column modifier to convert XPR helper character coords to pixel. */
       private double pixPerCol = XprHelper.getCol2Pix(DPI_DEFAULT, XprEntity.CPI_DEFAULT);

       /** Variable to keep tracking the next parameter number. */
       private long parameterCounter = 0;

       /** Map to match OS font prefix names to PDF generic font names. */
       private static Map<String, String[]> pdfFontMap = new HashMap<String, String[]>();

       /** Extra size for the font cell, depending on OS behind. */
       private static int fntExtra = !XprHelper.isSimpleUseMode() &&
                                     EnvironmentOps.isUnderWindowsFamily() ? 2 : 5;

       /** One time font mapping init. */
       static
       {
          // font map
          pdfFontMap.put("Arial", new String[] {"Helvetica", "Helvetica-Bold",
                                                "Helvetica-BoldOblique", "Helvetica-Oblique" });
          pdfFontMap.put("Times", new String[] {"Times-Roman", "Times-Bold",
                                                "Times-BoldItalic", "Times-Italic"});
          pdfFontMap.put("Courier", new String[] {"Courier", "Courier-Bold",
                                                  "Courier-BoldOblique", "Courier-Oblique"});
          pdfFontMap.put("Symbol", new String[] {"Symbol"});
          pdfFontMap.put("Dingbats", new String[] {"ZapfDingbats"});
       }

       /**
        * Creates new instance of the object with the given file name to export.
        *
        * @param   xprBase
        *          The XPR object to be the base for Jasper report creation.
        * @param   pdfName
        *          The PDF file object to export.
        */
       public Xpr2PdfWorker(PdfProvider xprBase, String pdfName)
       {
          pdfFileName = pdfName;
          xprBaseInput = xprBase;
       }

       /**
        * Maps the given OS dependent font name to OS independent PDF font.
        *
        * @param   fntName
        *          The name of the OS dependent font name to be mapped to PDF font.
        * @param   fntAttr
        *          The desired font attributes, normal, bold or italic.
        *
        * @return  The PDF font name to use or <code>NULL</code> if mapping is not possible.
        */
       public static String getMappedPdfFontName(String fntName, int fntAttr)
       {
          // NPE check
          if (fntName == null || fntName.isEmpty())
          {
             return null;
          }

          // walk through map to find the first good match
          for (String fntKey : pdfFontMap.keySet())
          {
             // fond close enough match
             if (fntName.indexOf(fntKey) != -1)
             {
                // get available pdf font falily array
                String[] pfdFontArr = pdfFontMap.get(fntKey);
                // if we have fons other than just normal
                if (pfdFontArr.length > fntAttr)
                {
                   // return PDF font name to use according requested attributes
                   return pfdFontArr[fntAttr];
                }
                else
                {
                   // return PDF font name to use, simple normal version
                   return pfdFontArr[0];
                }
             }
          }
          // nothing has found
          return null;
       }

       /**
        * Creates on the fly Jasper report and exports into PDF.
        *
        * @param   simpleUsage
        *          <code>TRUE</code> means simple using as standalone application for debugging.
        *
        * @return  <code>TRUE</code> if success, <code>FALSE</code> otherwise.
        */
       public boolean export(boolean simpleUsage)
       {
          boolean result = false;

          // multi page support
          List<JasperPrint> jspPrintList = new ArrayList<JasperPrint>();
          for (int i = 0; i < xprBaseInput.getTotalPageNumbers(); i++)
          {
             // create report design based on current XPR data
             JasperDesign jspDesign = makeJasperDesign(i);
             if (jspDesign == null)
             {
                return result;
             }

             // compile next page report
             JasperReport jspReport = null;
             try
             {
                jspReport = JasperCompileManager.compileReport(jspDesign);
             }
             catch (JRException jre)
             {
                XprHelper.displayOrLogError(String.format("Report compiling error for page %d", i),
                                            jre);
                return result;
             }

             // fill next page report with data
             JasperPrint jspPrint = null;
             try
             {
                jspPrint = JasperFillManager.fillReport(jspReport, jspParameters,
                                                        new JREmptyDataSource());
             }
             catch (JRException jre)
             {
                XprHelper.displayOrLogError(String.format("Report fill error for page %d", i), jre);
                return result;
             }
             // collect all filled pages
             jspPrintList.add(jspPrint);
          }
          // export report
          Stream rptStream = null;
          OutputStream outStreamBuff = null;
          if (!simpleUsage)
          {
             try
             {
                rptStream = StreamFactory.openFileStream(pdfFileName, true, false);
             }
             catch (ErrorConditionException ece)
             {
                XprHelper.displayOrLogError(
                   String.format("Error opening output stream for PDF file %s", pdfFileName), ece);
                return result;
             }

             outStreamBuff = new BufferedOutputStream(new OutputStreamWrapper(rptStream), 16384);
          }

          // only PDF exporter is in action for now
          try
          {
             JRPdfExporter exporter = new JRPdfExporter();
             if (simpleUsage)
             {
                exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(pdfFileName));
             }
             else
             {
                exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(outStreamBuff));
             }
             // pass oredered pages as input source
             exporter.setExporterInput(SimpleExporterInput.getInstance(jspPrintList));
             exporter.exportReport();
          }
          catch (JRException jre)
          {
             XprHelper.displayOrLogError(
                String.format("Report export error for file %s", pdfFileName), jre);
             return result;
          }
          finally
          {
             try
             {
                if (!simpleUsage)
                {
                   outStreamBuff.flush();
                   outStreamBuff.close();
                }
             }
             catch (IOException ioe)
             {
                XprHelper.displayOrLogError(
                   String.format("Stream closing error for file %s", pdfFileName), ioe);
                return result;
             }
          }

          result = true;
          return result;
       }

       /**
        * Creates on the fly Jasper report.
        *
        * @param   pageNum
        *          0 based page number to prepare on the fly report.
        *
        * @return  jasper design object for current XPR data.
        */
       private JasperDesign makeJasperDesign(int pageNum)
       {
          // temporary variables used while design creation
          JRDesignBand band = null;
          // start using new Jasper design
          JasperDesign resDesign = new JasperDesign();
          resDesign.setName(pdfFileName.concat("_InMemoryReport"));
          // set up page properties
          // page size, the page margins to be defined in directory
          int pageWidth = xprBaseInput.getPageWidth(pageNum);
          // error while getting current page width
          if (pageWidth == -1)
          {
             pageWidth = XprHelper.getPdfPageWidth();
          }
          int pageHeight = xprBaseInput.getPageHeight(pageNum);
          // error while getting current page height
          if (pageHeight == -1)
          {
             pageHeight = XprHelper.getPdfPageHeight();
          }
          resDesign.setPageWidth(pageWidth);
          resDesign.setPageHeight(pageHeight);
          // 0 for all margins and spacing
          resDesign.setColumnSpacing(0);
          resDesign.setLeftMargin(0);
          resDesign.setRightMargin(0);
          resDesign.setTopMargin(0);
          resDesign.setBottomMargin(0);

          // title
          band = new JRDesignBand();
          resDesign.setTitle(band);

          // page header
          band = new JRDesignBand();
          resDesign.setPageHeader(band);

          // column header
          band = new JRDesignBand();
          resDesign.setColumnHeader(band);

          // detail
          band = new JRDesignBand();
          band.setHeight(pageHeight);
          // we will use only one band for all objects in page
          setObjectsInBand(band, xprBaseInput.getObjectsInPage(pageNum), resDesign);
          // adding completed band to the report
          ((JRDesignSection)resDesign.getDetailSection()).addBand(band);

          // column footer
          band = new JRDesignBand();
          resDesign.setColumnFooter(band);

          // page footer
          band = new JRDesignBand();
          resDesign.setPageFooter(band);

          // summary
          band = new JRDesignBand();
          resDesign.setSummary(band);

          return resDesign;
       }

       /**
        * Creates new unique parameter name for Jasper report.
        *
        * @return  jasper design parameter name.
        */
       private String getNewParameterName()
       {
          StringBuilder sb = new StringBuilder();
          // same prefix for all parameters
          sb.append("Xpr2PdfParameter");
          // but different suffix
          sb.append(++parameterCounter);

          return sb.toString();
       }

       /**
        * Adding different objects to the given Jasper report band keeping Z-order.
        *
        * @param   objBand
        *          The Jasper report band to prepare.
        * @param   objectsToAdd
        *          The list of objects to add.
        * @param   jspDesign
        *          The current design in use.
        */
       private void setObjectsInBand(JRDesignBand objBand, List<XprObjBase> objectsToAdd,
                                     JasperDesign jspDesign)
       {
          // walk through the objects from start to end
          for (int i = 0; i < objectsToAdd.size(); i++)
          {
             XprObjBase xprObj = objectsToAdd.get(i);
             // this is object dependent set up procedure
             switch (xprObj.getType())
             {
                case XprObjBase.TYPE_LINE:
                   setLineInBand(objBand, (XprObjLine)xprObj);
                   break;
                case XprObjBase.TYPE_RECTANGLE:
                   setRectangleInBand(objBand, (XprObjRectangle)xprObj, false);
                   break;
                case XprObjBase.TYPE_FILLED_RECTANGLE:
                   setRectangleInBand(objBand, (XprObjFilledRectangle)xprObj, true);
                   break;
                case XprObjBase.TYPE_TEXT:
                   setTextInBand(objBand, (XprObjText)xprObj, jspDesign);
                   break;
                case XprObjBase.TYPE_IMAGE:
                   setImageInBand(objBand, (XprObjImage)xprObj, jspDesign);
                   break;
                case XprObjBase.TYPE_BARCODE:
                   setBarcodeInBand(objBand, (XprObjBarcode)xprObj, jspDesign);
                   break;
             }
          }
       }

       /**
        * Adding line object to the given Jasper report band as next one.
        *
        * @param   objBand
        *          The Jasper report band to prepare.
        * @param   objLineToAdd
        *          The line object to add.
        */
       private void setLineInBand(JRDesignBand objBand, XprObjLine objLineToAdd)
       {
          // reconstruct the line
          JRDesignLine line = new JRDesignLine();

          // coordinates and size
          double x1 = objLineToAdd.getColumn() - 1.0;
          double x2 = objLineToAdd.getColumnEnd() - 1.0;
          double y1 = objLineToAdd.getRow() - 1.0;
          double y2 = objLineToAdd.getRowEnd() - 1.0;
          // x, y requires floor() to compute, may be dx, dy too
          int x = (int)Math.floor((x1 + objLineToAdd.getLeftMargin()) * pixPerCol);
          int y = (int)Math.floor((y1 + objLineToAdd.getTopMargin()) * pixPerRow);
          int dx = (int)Math.round((x2-x1) * pixPerCol);
          int dy = (int)Math.round((y2-y1) * pixPerRow);
          line.setX(x);
          line.setY(y);
          line.setWidth(dx);
          line.setHeight(dy);
          line.setForecolor(new Color(objLineToAdd.getColor()));
          // do we really need the line width, ignore for now?
          line.getLinePen().setLineWidth(objLineToAdd.getLineWidth());
          line.getLinePen().setLineStyle(objLineToAdd.getLineStyle());

          // add new line to the band
          objBand.addElement(line);
       }

       /**
        * Adding line object to the given Jasper report band as next one.
        *
        * @param   objBand
        *          The Jasper report band to prepare.
        * @param   objRectangleToAdd
        *          The rectangle object to add.
        * @param   isFilled
        *          <code>TRUE</code> for filled recteangle, <code>FALSE</code> for frame.
        */
       private void setRectangleInBand(JRDesignBand objBand, XprObjRectangle objRectangleToAdd,
                                       boolean isFilled)
       {
          // reconstruct the rect
          JRDesignRectangle rect = new JRDesignRectangle();

          // coordinates and size
          double x1 = objRectangleToAdd.getColumn() - 1.0;
          double x2 = objRectangleToAdd.getColumnEnd() - 1.0;
          double y1 = objRectangleToAdd.getRow() - 1.0;
          double y2 = objRectangleToAdd.getRowEnd() - 1.0;
          // x, y requires floor() to compute, may be dx, dy too
          int x = (int)Math.floor((x1 + objRectangleToAdd.getLeftMargin()) * pixPerCol);
          int y = (int)Math.floor((y1 + objRectangleToAdd.getTopMargin()) * pixPerRow);
          int dx = (int)Math.round((x2-x1) * pixPerCol);
          int dy = (int)Math.round((y2-y1) * pixPerRow);
          rect.setX(x);
          rect.setY(y);
          rect.setWidth(dx);
          rect.setHeight(dy);
          rect.setForecolor(new Color(objRectangleToAdd.getColor()));
          // line width
          rect.getLinePen().setLineWidth(objRectangleToAdd.getRectLineWidth());
          rect.getLinePen().setLineStyle(objRectangleToAdd.getRectLineStyle());
          // disable fill for framed rerctangle
          if (isFilled)
          {
             rect.setBackcolor(new Color(((XprObjFilledRectangle)objRectangleToAdd).getFillColor()));
          }
          else
          {
             rect.setMode(ModeEnum.TRANSPARENT);
          }
          // set up rounded rectangle
          if (objRectangleToAdd.isRounded())
          {
             // the rounded radius computing approach:
             // - get the minimum af both directions
             // - take 1/4 of the size as radius
             rect.setRadius((int)Math.round((double)Math.min(dx, dy) * XprObjRectangle.RADIUS_SCALE));
          }

          // add new line to the band
          objBand.addElement(rect);
       }

       /**
        * Adding text object to the given Jasper report band as next one.
        *
        * @param   objBand
        *          The Jasper report band to prepare.
        * @param   objTextToAdd
        *          The text object to add.
        * @param   jspDesign
        *          The current design in use.
        */
       private void setTextInBand(JRDesignBand objBand, XprObjText objTextToAdd,
                                  JasperDesign jspDesign)
       {
          // reconstruct the text as static one
          double x1 = objTextToAdd.getColumn() - 1.0;
          double y1 = objTextToAdd.getRow() - 1.0;
          // x, y requires floor() to compute, may be dx, dy too
          int x = (int)(Math.floor((x1 + objTextToAdd.getLeftMargin()) * pixPerCol));
          int y = (int)(Math.floor((y1  + objTextToAdd.getTopMargin()) * pixPerRow));
          // we need extra pixels to make text visible if font size is bigger than row
          // compute them as 1/fntExtra of the font size
          float fontSizeFloat = objTextToAdd.getFontSize();
          int fntSize = (int)Math.round(fontSizeFloat);
          int dy = Math.max((int)Math.round(pixPerRow), fntSize + (fntSize + fntExtra)/fntExtra);
          int dx = (int)Math.max((int)Math.round(pixPerCol),
                   (fntSize + fntExtra)/fntExtra + (int)(pixPerCol*(double)fntSize/pixPerRow));
          // text to display
          String textLine = objTextToAdd.getValue();
          // need to exclude text that outside the page area
          if (x > jspDesign.getPageWidth() - 1 || y > jspDesign.getPageHeight() - 1 ||
              y + dy > jspDesign.getPageHeight() - 1)
          {
             XprHelper.displayOrLogError(
                String.format("Text object %s is out of the page area.", textLine));
             return;
          }
          JRDesignStaticText txt = new JRDesignStaticText();
          txt.setX(x);
          txt.setY(y);
          txt.setHeight(dy);
          txt.setWidth(dx * textLine.length());
          // try to use OS independent font name
          boolean isBold = objTextToAdd.isBold();
          boolean isItalic = objTextToAdd.isItalic();
          int fontStyle = isBold && isItalic ? FONT_STYLE_BOLD_ITALIC
                                             : isBold ? FONT_STYLE_BOLD
                                                      : isItalic ? FONT_STYLE_ITALIC
                                                                 : FONT_STYLE_NORMAL;
          String pdfFntName = getMappedPdfFontName(objTextToAdd.getFontName(), fontStyle);
          if (pdfFntName != null)
          {
             txt.setPdfFontName(pdfFntName);
          }
          txt.setFontSize(fontSizeFloat);
          txt.setForecolor(new Color(objTextToAdd.getColor()));
          txt.setText(textLine);
          objBand.addElement(txt);
       }

       /**
        * Adding image object to the given Jasper report band as next one.
        *
        * @param   objBand
        *          The Jasper report band to prepare.
        * @param   objImageToAdd
        *          The image object to add.
        * @param   jspDesign
        *          The current design in use.
        */
       private void setImageInBand(JRDesignBand objBand, XprObjImage objImageToAdd,
                                   JasperDesign jspDesign)
       {
          boolean isPDFImage = false;
          // reconstruct the image
          JRDesignImage img = new JRDesignImage(jspDesign);

          // coordinates and size
          double x1 = objImageToAdd.getColumn() - 1.0;
          double x2 = objImageToAdd.getColumnEnd() - 1.0;
          double y1 = objImageToAdd.getRow() - 1.0;
          double y2 = objImageToAdd.getRowEnd() - 1.0;
          // need to support possible coord reverse
          // x, y requires floor() to compute, may be dx, dy too
          int x = (int)Math.floor((Math.min(x1, x2) + objImageToAdd.getLeftMargin()) * pixPerCol);
          int y = (int)Math.floor((Math.min(y1, y2) + objImageToAdd.getTopMargin()) * pixPerRow);
          int dx = (int)Math.round(Math.abs(x2-x1) * pixPerCol);
          int dy = (int)Math.round(Math.abs(y2-y1) * pixPerRow);
          img.setX(x);
          img.setY(y);
          int pageHeight = jspDesign.getPageHeight();
          int pageWidth = jspDesign.getPageWidth();
          // check if the image is not outside the page and compute scaling coeff
          int exDy = y + dy - pageHeight + 1;
          double kYCut = 1.0;
          if (exDy > 0)
          {
             kYCut = (double)(dy - exDy) / (double)(dy);
             dy -= exDy;
          }
          int exDx = x + dx - pageWidth + 1;
          double kXCut = 1.0;
          if (exDx > 0)
          {
             kXCut = (double)(dx - exDx) / (double)(dx);
             dx -= exDx;
          }
          img.setWidth(dx);
          img.setHeight(dy);
          img.setScaleImage(ScaleImageEnum.RETAIN_SHAPE);

          // images should be handled via report parameter
          String imagePath = objImageToAdd.getLocationName();
          // find out if embedded image is pdf
          int extNdx = imagePath.lastIndexOf(".");
          if (extNdx != -1 && extNdx < imagePath.length() - 3)
          {
             String imgExtension = imagePath.substring(extNdx + 1);
             if (imgExtension != null && imgExtension.toUpperCase().equals("PDF"))
             {
                isPDFImage = true;
             }
          }
          String paramName = getNewParameterName();
          // insert parameter into design
          JRDesignParameter param = new JRDesignParameter();
          param.setName(paramName);
          // call is deprecated, no more required according to Jaspersoft
          BufferedImage imgToUse = null;
          if (isPDFImage)
          {
             param.setValueClass(java.awt.Image.class);
          }
          else
          {
             // try to get the image stream from application jar
             InputStream isImg = FwdJasperExtensionRegistry.getRepositoryService()
                                                           .getInputStream(imagePath);
             if (isImg != null)
             {
                byte[] imgBytes = UiUtils.getData(isImg);
                if (imgBytes != null && imgBytes.length > 0)
                {
                   imgToUse = AbstractGuiDriver.createImage(imgBytes).getImage();
                }
             }

             if (imgToUse != null)
             {
                param.setValueClass(java.awt.Image.class);
             }
             else
             {
                param.setValueClass(java.lang.String.class);
             }
          }
          try
          {
             jspDesign.addParameter(param);
          }
          catch (JRException jre)
          {
             XprHelper.displayOrLogError("Image parameter adding error for JasperDesign", jre);
             return;
          }

          // insert expression into design
          JRDesignExpression expr = new JRDesignExpression();
          // call is deprecated, no more required according to Jaspersoft
    //      expr.setValueClass(java.lang.String.class);
          expr.setText("$P{".concat(paramName).concat("}"));
          img.setExpression(expr);
          if (XprHelper.onErrorImageSilentMode())
          {
             img.setOnErrorType(OnErrorTypeEnum.ICON);
          }
          // add new line to the band
          objBand.addElement(img);
          // set up new parameter
          if(isPDFImage)
          {
             // get the input stream from helper
             InputStream is = XprHelper.getInputStream(imagePath);
             if (is == null)
             {
                XprHelper.displayOrLogError(
                   String.format("Unable to get input stream from image path %s", imagePath));
                return;
             }
             // set up PDF reader
             try
             {
                PDDocument document = PDDocument.load(is);
                Iterator<PDPage> pdPageIterator = document.getDocumentCatalog().getPages().iterator();
                List<BufferedImage> images = new ArrayList<>();
                while (pdPageIterator.hasNext())
                {
                   PDPage page = pdPageIterator.next();
                   PDResources resources = page.getResources();
                   for (COSName nextObjName : resources.getXObjectNames())
                   {
                      if (resources.isImageXObject(nextObjName))
                      {
                         PDXObject pdxImage = resources.getXObject(nextObjName);
                         byte[] pdxImageBytes = pdxImage.getStream().toByteArray();
                         BufferedImage image = ImageIO.read(new ByteArrayInputStream(pdxImageBytes));
                         images.add(image);
                      }
                   }
                }

                // take a first image with truncating
                if ( kXCut != 1.0 || kYCut != 1.0 )
                {
                   // we need to truncate the image a bit
                   BufferedImage bi = images.get(0);
                   imgToUse = bi.getSubimage(0, 0, (int)(kXCut * (double)bi.getWidth()),
                                             (int)(kYCut * (double)bi.getHeight()));
                }
                else
                {
                   imgToUse = images.get(0);
                }
                jspParameters.put(paramName, imgToUse);
             }
             catch (Exception e)
             {
                XprHelper.displayOrLogError(String.format("PDF file %s loading error", imagePath), e);
                return;
             }
          }
          else
          {
             if (imgToUse != null)
             {
                jspParameters.put(paramName, imgToUse);
             }
             else
             {
                jspParameters.put(paramName, imagePath);
             }
          }
       }

       /**
        * Adding barcode object to the given Jasper report band as next one.
        *
        * @param   objBand
        *          The Jasper report band to prepare.
        * @param   objBarcodeToAdd
        *          The barcode object to add.
        * @param   jspDesign
        *          The current design in use.
        */
       private void setBarcodeInBand(JRDesignBand objBand, XprObjBarcode objBarcodeToAdd,
                                     JasperDesign jspDesign)
       {
          // check for barcode type
          String type = objBarcodeToAdd.getBarcodeType();
          if (type == null || type.isEmpty())
          {
             // barcode type is invalid, no reasons to continue
             XprHelper.displayOrLogError("Barcode type is invalid");
             return;
          }

          // reconstruct the barcode
          StandardBarbecueComponent bcBBCComp = new StandardBarbecueComponent();

          // set type
          bcBBCComp.setType(type);
          // barcodes should be handled via report parameter
          String barcodeValue = objBarcodeToAdd.getParameterValue("VALUE");
          String paramName = getNewParameterName();
          // insert parameter into design
          JRDesignParameter param = new JRDesignParameter();
          param.setName(paramName);
          param.setValueClass(java.lang.String.class);
          try
          {
             jspDesign.addParameter(param);
          }
          catch (JRException jre)
          {
             XprHelper.displayOrLogError("Barcode parameter adding error for JasperDesign", jre);
             return;
          }

          // checksum and show text
          bcBBCComp.setChecksumRequired(objBarcodeToAdd.isChecksum());
          bcBBCComp.setDrawText(objBarcodeToAdd.isShow());

          // insert expression into design
          JRDesignExpression expr = new JRDesignExpression();
          // call is deprecated, no more required according to Jaspersoft doc
    //      expr.setValueClass(java.lang.String.class);
          expr.setText("$P{".concat(paramName).concat("}"));
          bcBBCComp.setCodeExpression(expr);

          // coordinates and size
          double x1 = objBarcodeToAdd.getColumn() - 1.0;
          double x2 = objBarcodeToAdd.getColumnEnd() - 1.0;
          double y1 = objBarcodeToAdd.getRow() - 1.0;
          double y2 = objBarcodeToAdd.getRowEnd() - 1.0;
          // x, y requires floor() to compute, may be dx, dy too
          int x = (int)Math.floor((x1 + objBarcodeToAdd.getLeftMargin()) * pixPerCol);
          int y = (int)Math.floor((y1 + objBarcodeToAdd.getTopMargin()) * pixPerRow);
          int dx = (int)Math.round((x2-x1) * pixPerCol);
          int dy = (int)Math.round((y2-y1) * pixPerRow);
          // dimensions for barcode
          // the bar width option is the minimum element(single line) width, assume to be 1 pixel
          // or more depending on size ratio and barcode type
          int bWidth = objBarcodeToAdd.getBarWidth(dx, dy);
          if (bWidth > 0)
          {
             bcBBCComp.setBarWidth(bWidth);
          }
          // barcode width means the height might be adjusted too
          if (bWidth > 1)
          {
             bcBBCComp.setBarHeight(objBarcodeToAdd.getBarHeight(dx, dy));
          }
          else
          {
             bcBBCComp.setBarHeight(dy);
          }
          // create design element wrapper
          JRDesignComponentElement jrComp = new JRDesignComponentElement();
          // location and size
          jrComp.setX(x);
          jrComp.setY(y);
          // set up barcode rotation and enclosed element size
          String angle = objBarcodeToAdd.getParameterValue("ANGLE");
          if (!angle.isEmpty())
          {
             if (angle.equals("90"))
             {
                jrComp.setWidth(dy);
                jrComp.setHeight(dx);
                bcBBCComp.setRotation(RotationEnum.LEFT);
             }
             else if (angle.equals("180"))
             {
                jrComp.setWidth(dx);
                jrComp.setHeight(dy);
                bcBBCComp.setRotation(RotationEnum.UPSIDE_DOWN);
             }
             else if (angle.equals("270"))
             {
                jrComp.setWidth(dy);
                jrComp.setHeight(dx);
                bcBBCComp.setRotation(RotationEnum.RIGHT);
             }
             else if (!angle.equals("0") && !angle.equals("360"))
             {
                // unsupported angle here
                XprHelper.displayOrLogError(
                   String.format("Barcode angle %s is not supported.", angle));
             }
          }
          else
          {
             jrComp.setWidth(dx);
             jrComp.setHeight(dy);
          }
          // insert barcode into design component to be added
          jrComp.setComponent(bcBBCComp);
          jrComp.setComponentKey(
             new ComponentKey(ComponentsExtensionsRegistryFactory.NAMESPACE, "c",
                              ComponentsExtensionsRegistryFactory.BARBECUE_COMPONENT_NAME));
          // add new line to the band
          objBand.addElement(jrComp);
          // set up new parameter
          jspParameters.put(paramName, barcodeValue);
       }
    }