MetafileHelper.java

/*
** Module   : MetafileHelper.java
** Abstract : Helper class to provide report generation from generic GUI metafile.
**
** Copyright (c) 2019-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------------Description---------------------------------------
** 001 EVL 20190510 Created initial version.
** 002 EVL 20190618 Removed printDoc and sendOptionalText from metafile extension set.
** 003 EVL 20201216 Added more clean up for metafile stop/interrupt processing.
**     EVL 20210106 Added code to properly finalize the metafile recording.
**     EVL 20210107 Added new helper methods to get current temporary directory and reset text styles.
**     EVL 20210113 Added new helper method to calculate text scaling factor.
**     EVL 20210114 Changed to use common constant from Xpr2PdfWorker.
**     EVL 20210602 Added setPageOrientation(character) method.
**     GES 20210610 Added setTextStyle(character) method.
**     EVL 20211021 Making small improvement for text width calculation with Arial font.
**     EVL 20211026 Changing text font size type to float.
**     EVL 20211115 Improved handling of the ' ' char weight while text width calculation.
**     EVL 20220126 Some fixes for text width calculation approach.
**     EVL 20220127 Adding method to get current text underline attribute.  Fix text width for certain very
**                  narrow chars.
**     EVL 20220227 More separations for wide, extra wide and regular characters fro capital letters.
**     EVL 20220228 Adding methods version with 4GL compatible input parameters types.
**     EVL 20220302 Moving here the additional processing for bold and italic fonts.
**     EVL 20220314 Adding two more characters to very narrow character set for weight calculation.
**     EVL 20220315 Reviewed printable characters size based on general sample and changes some sets to
**                  increase match and precision.
** 004 DDF 20230620 Replaced static initialization of values from the directory configuration with
**                  a method called at server bootstrap.
** 005 EVL 20240730 Adding ability to have preview only instead of real file downloading.
*/

/*
** 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.security.*;
import com.goldencode.p2j.ui.client.*;
import com.goldencode.p2j.util.*;

import java.io.*;

/**
 * A helper class to convert and show a generic GDI metafile as a PDF document. This replacement is platform
 * neutral (not dependent upon WIN32).
 */
public class MetafileHelper
{
   /** Base units is inches constant. */
   private static final int UNITS_INCHES = 0;
   
   /** Base units is cm constant. */
   private static final int UNITS_CM = 1;

   /** Base units is mm constant. */
   private static final int UNITS_MM = 2;
   
   /** Row modifier to convert XPR helper row coords to pixel. */
   private static double pixPerRow = XprHelper.getRow2Pix(Xpr2PdfWorker.DPI_DEFAULT,
                                                          XprEntity.LPI_DEFAULT);
   
   /** Column modifier to convert XPR helper character coords to pixel. */
   private static double pixPerCol = XprHelper.getCol2Pix(Xpr2PdfWorker.DPI_DEFAULT,
                                                          XprEntity.CPI_DEFAULT);
   
   /** Base units type, inches, cm or mm. */
   private static int units = UNITS_CM;
   
   /** Stores context-local state variables. */
   private static ContextContainer work = new ContextContainer(); 
   
   /** Flag to open real file or make preview. */
   private static boolean openFile = true;
   
   /**
    * Method called at server bootstrap that initializes values from the directory
    * configuration. Until this method is called, default values are used.
    */
   public static void bootstrap()
   {
      // several options can be overridden in directory
      // metafile units override
      String unitsOverride = Utils.getDirectoryNodeString(null, "metafile-report/units", null,
                                                          Utils.DirScope.BOTH, null);
      if (unitsOverride != null && !unitsOverride.isEmpty())
      {
         if(unitsOverride.equalsIgnoreCase("INCHES"))
         {
            units = UNITS_INCHES;
         }
         else if(unitsOverride.equalsIgnoreCase("MM"))
         {
            units = UNITS_MM;
         }
         else if(unitsOverride.equalsIgnoreCase("CM"))
         {
            units = UNITS_CM;
         }
      }
   }

   /**
    * Sets new values for text alignment.
    * 
    * @param    horizAlign
    *           The new String value for horizontal text alignment.  The valid alignment values:
    *           "left", "center", "right".
    * @param    vertAlign
    *           The new String value for vertical text alignment.  The valid alignment values:
    *           "top", "baseline", "bottom".
    */
   public static void setTextAlignment(String horizAlign, String vertAlign)
   {
      resolveCurrentMetafile().setTextAlignment(horizAlign, vertAlign);
   }

   /**
    * Sets new values for text alignment.
    * 
    * @param    horizAlign
    *           The new String value for horizontal text alignment.  The valid alignment values:
    *           "left", "center", "right".
    * @param    vertAlign
    *           The new String value for vertical text alignment.  The valid alignment values:
    *           "top", "baseline", "bottom".
    */
   public static void setTextAlignment(character horizAlign, String vertAlign)
   {
      if (isParameterValid(horizAlign))
      {
         setTextAlignment(horizAlign.getValue(), vertAlign);
      }
   }

   /**
    * Sets new values for text alignment.
    * 
    * @param    horizAlign
    *           The new String value for horizontal text alignment.  The valid alignment values:
    *           "left", "center", "right".
    * @param    vertAlign
    *           The new String value for vertical text alignment.  The valid alignment values:
    *           "top", "baseline", "bottom".
    */
   public static void setTextAlignment(String horizAlign, character vertAlign)
   {
      if (isParameterValid(vertAlign))
      {
         setTextAlignment(horizAlign, vertAlign.getValue());
      }
   }

   /**
    * Sets new values for text alignment.
    * 
    * @param    horizAlign
    *           The new String value for horizontal text alignment.  The valid alignment values:
    *           "left", "center", "right".
    * @param    vertAlign
    *           The new String value for vertical text alignment.  The valid alignment values:
    *           "top", "baseline", "bottom".
    */
   public static void setTextAlignment(character horizAlign, character vertAlign)
   {
      if (isParameterValid(horizAlign) && isParameterValid(vertAlign))
      {
         setTextAlignment(horizAlign.getValue(), vertAlign.getValue());
      }
   }

   /**
    * Sets new current color value to be used in text primitives.
    * 
    * @param    rgbColor
    *           The new 32-bit color value packed in int value.
    */
   public static void setTextColor(int rgbColor)
   {
      resolveCurrentMetafile().setTextColor(rgbColor);
   }

   /**
    * Sets new current color value to be used in text primitives.
    * 
    * @param    rgbColor
    *           The new 32-bit color value packed in int value.
    */
   public static void setTextColor(integer rgbColor)
   {
      if (isParameterValid(rgbColor))
      {
         setTextColor(rgbColor.intValue());
      }
   }

   /**
    * Sets new value for text style.
    * 
    * @param    style
    *           The new String value for the current text style.  The valid style values:
    *           "italic", "underline", "bold", "no-italic", "no-underline", "no-bold".
    */
   public static void setTextStyle(String style)
   {
      resolveCurrentMetafile().setTextStyle(style);
   }

   /**
    * Sets new value for text style.
    * 
    * @param    style
    *           The new String value for the current text style.  The valid style values:
    *           "italic", "underline", "bold", "no-italic", "no-underline", "no-bold".
    */
   public static void setTextStyle(character style)
   {
      if (style == null || style.isUnknown())
      {
         return;
      }
      
      setTextStyle(style.toStringMessage());
   }

   /**
    * Gets flag meaning underlined text should be currently used.
    * 
    * @return   <code>TRUE</code> if text underlined, <code>FALSE</code> otherwise.
    */
   public static boolean isUnderline()
   {
      return resolveCurrentMetafile().isUnderline();
   }
   
   /**
    * Resets new value for text style to be off.
    */
   public static void resetTextStyle()
   {
      resolveCurrentMetafile().resetTextStyle();
   }
   
   /**
    * Starts the new page construction for the document that is currently processing.
    */
   public static void startNewPage()
   {
      resolveCurrentMetafile().startNewPage();
   }

   /**
    * Sets new value for current font.
    * 
    * @param    fontName
    *           The new String value for the current font name.  Can be "default" for default
    *           font to use.
    */
   public static void setFont(String fontName)
   {
      resolveCurrentMetafile().setFont(fontName);
   }

   /**
    * Sets new value for current font.
    * 
    * @param    fontName
    *           The new String value for the current font name.  Can be "default" for default
    *           font to use.
    */
   public static void setFont(character fontName)
   {
      if (isParameterValid(fontName))
      {
         setFont(fontName.getValue());
      }
   }

   /**
    * Gets the current font value.
    * 
    * @param    fontName
    *           The value for the font name currently in use.
    */
   public static void getFont(character fontName)
   {
      resolveCurrentMetafile().getFont(fontName);
   }

   /**
    * Sets new value for current zoom factor.
    * 
    * @param    zoomType
    *           The new String value for the current zoom type.
    */
   public static void setZoomFactor(String zoomType)
   {
      resolveCurrentMetafile().setZoomFactor(zoomType);
   }

   /**
    * Sets new value for current zoom factor.
    * 
    * @param    zoomType
    *           The new String value for the current zoom type.
    */
   public static void setZoomFactor(character zoomType)
   {
      if (isParameterValid(zoomType))
      {
         setZoomFactor(zoomType.getValue());
      }
   }

   /**
    * Gets the current zoom factor value.
    * 
    * @param    zoomType
    *           The value for the zoom type currently in use.
    */
   public static void getZoomFactor(character zoomType)
   {
      resolveCurrentMetafile().getZoomFactor(zoomType);
   }

   /**
    * Gets the current font height value.
    * 
    * @param    fontHeight
    *           The currently used font height value in metric units.
    */
   public static void getFontHeight(decimal fontHeight)
   {
      resolveCurrentMetafile().getFontHeight(fontHeight);
   }

   /**
    * Sets the current font height value.
    * 
    * @param    fontHeight
    *           The font height value in pics to use.
    */
   public static void setFontHeight(float fontHeight)
   {
      resolveCurrentMetafile().setFontHeight(fontHeight);
   }

   /**
    * Sets the current font height value.
    * 
    * @param    fontHeight
    *           The font height value in pics to use.
    */
   public static void setFontHeight(integer fontHeight)
   {
      if (isParameterValid(fontHeight))
      {
         setFontHeight(fontHeight.intValue());
      }
   }

   /**
    * Gets the given text width value.
    * 
    * @param    textToMeasure
    *           The text to calculate width.
    * @param    textWidth
    *           The width of the text in metric units.
    */
   public static void getTextWidth(String textToMeasure, decimal textWidth)
   {
      resolveCurrentMetafile().getTextWidth(textToMeasure, textWidth);
   }

   /**
    * Gets the given text width value.
    * 
    * @param    textToMeasure
    *           The text to calculate width.
    * @param    textWidth
    *           The width of the text in metric units.
    */
   public static void getTextWidth(character textToMeasure, decimal textWidth)
   {
      if (isParameterValid(textToMeasure))
      {
         getTextWidth(textToMeasure.getValue(), textWidth);
      }
   }

   /**
    * Sets the new current position for cursor.  Can be used a starting point for text/line/image
    * draw.
    * 
    * @param    x
    *           The new X position.
    * @param    y
    *           The new Y position.
    */
   public static void setXY(double x, double y)
   {
      resolveCurrentMetafile().setXY(x, y);
   }

   /**
    * Sets the new current position for cursor.  Can be used a starting point for text/line/image
    * draw.
    * 
    * @param    x
    *           The new X position.
    * @param    y
    *           The new Y position.
    */
   public static void setXY(decimal x, decimal y)
   {
      if (isParameterValid(x) && isParameterValid(y))
      {
         setXY(x.doubleValue(), y.doubleValue());
      }
   }

   /**
    * Sets the new current position for cursor.  Can be used a starting point for text/line/image
    * draw.
    * 
    * @param    x
    *           The new X position.
    * @param    y
    *           The new Y position.
    */
   public static void setXY(double x, decimal y)
   {
      if (isParameterValid(y))
      {
         setXY(x, y.doubleValue());
      }
   }

   /**
    * Sets the new current position for cursor.  Can be used a starting point for text/line/image
    * draw.
    * 
    * @param    x
    *           The new X position.
    * @param    y
    *           The new Y position.
    */
   public static void setXY(decimal x, double y)
   {
      if (isParameterValid(x))
      {
         setXY(x.doubleValue(), y);
      }
   }

   /**
    * Sets the new value for document's left margin.
    * 
    * @param    leftMargin
    *           The new left margin value.
    */
   public static void setLeftMargin(double leftMargin)
   {
      resolveCurrentMetafile().setLeftMargin(leftMargin);
   }

   /**
    * Sets the new value for document's left margin.
    * 
    * @param    leftMargin
    *           The new left margin value.
    */
   public static void setLeftMargin(decimal leftMargin)
   {
      if (isParameterValid(leftMargin))
      {
         setLeftMargin(leftMargin.doubleValue());
      }
   }

   /**
    * Gets the new current position for cursor.  Can be used a starting point for text/line/image
    * draw.
    * 
    * @param    x
    *           The returned X position.
    * @param    y
    *           The returned Y position.
    */
   public static void getXY(decimal x, decimal y)
   {
      resolveCurrentMetafile().getXY(x, y);
   }

   /**
    * Starts new line of the text.  Y coord will be shifted by the text height and interleaving,
    * X coored be set to leftmost point of the text.
    */
   public static void startNewTextLine()
   {
      resolveCurrentMetafile().startNewTextLine();
   }

   /**
    * Sets the extra vertical space between two sequential rows.
    * 
    * @param    extraYSpace
    *           The new extra Y distance between rows.
    */
   public static void setInterleaving(double extraYSpace)
   {
      resolveCurrentMetafile().setInterleaving(extraYSpace);
   }

   /**
    * Sets the extra vertical space between two sequential rows.
    * 
    * @param    extraYSpace
    *           The new extra Y distance between rows.
    */
   public static void setInterleaving(decimal extraYSpace)
   {
      if (isParameterValid(extraYSpace))
      {
         setInterleaving(extraYSpace.doubleValue());
      }
   }

   /**
    * Gets the free space below the current Y position.
    * 
    * @param    freeSpaceDown
    *           The remaining vertical free space on the current page.
    */
   public static void getFreeSpaceDown(decimal freeSpaceDown)
   {
      resolveCurrentMetafile().getFreeSpaceDown(freeSpaceDown);
   }

   /**
    * Gets the free space to the right the current X position.
    * 
    * @param    freeSpaceRight
    *           The remaining horizontal free space on the current line(page).
    */
   public static void getFreeSpaceRight(decimal freeSpaceRight)
   {
      resolveCurrentMetafile().getFreeSpaceRight(freeSpaceRight);
   }

   /**
    * Draws the text at current position.
    * 
    * @param    text
    *           The new text to draw at current position.
    */
   public static void drawText(String text)
   {
      resolveCurrentMetafile().drawText(text);
   }

   /**
    * Draws the text at current position.
    * 
    * @param    text
    *           The new text to draw at current position.
    */
   public static void drawText(character text)
   {
      if (isParameterValid(text))
      {
         drawText(text.getValue());
      }
   }
   
   /**
    * Draws the text at current position.
    * 
    * @param    x
    *           The X position to set after text drawing in device independent units.
    * @param    text
    *           The new text to draw at current position.
    */
   public static void drawTextAndSetX(double x, String text)
   {
      resolveCurrentMetafile().drawTextAndSetX(x, text);
   }

   /**
    * Draws the text at current position.
    * 
    * @param    x
    *           The X position to set after text drawing in device independent units.
    * @param    text
    *           The new text to draw at current position.
    */
   public static void drawTextAndSetX(decimal x, character text)
   {
      if (isParameterValid(x) && isParameterValid(text))
      {
         drawTextAndSetX(x.doubleValue(), text.getValue());
      }
   }

   /**
    * Draws the text at the given X position and current Y position and given alignment.
    * 
    * @param    x
    *           The X position for text to draw in device independent units.
    * @param    text
    *           The new text to draw at the given position.
    * @param    align
    *           The alignment value, either left or right.
    */
   public static void drawTextAtX(double x, String text, String align)
   {
      resolveCurrentMetafile().drawTextAtX(x, text, align);
   }

   /**
    * Draws the text at the given X position and current Y position and left alignment.
    * 
    * @param    x
    *           The X position for text to draw in device independent units.
    * @param    text
    *           The new text to draw at the given position.
    */
   public static void drawTextAtXLeft(double x, String text)
   {
      resolveCurrentMetafile().drawTextAtX(x, text, "left");
   }

   /**
    * Draws the text at the given X position and current Y position and left alignment.
    * 
    * @param    x
    *           The X position for text to draw in device independent units.
    * @param    text
    *           The new text to draw at the given position.
    */
   public static void drawTextAtXLeft(decimal x, character text)
   {
      if (isParameterValid(x) && isParameterValid(text))
      {
         drawTextAtXLeft(x.doubleValue(), text.getValue());
      }
   }

   /**
    * Draws the text at the given X position and current Y position and left alignment.
    * 
    * @param    x
    *           The X position for text to draw in device independent units.
    * @param    text
    *           The new text to draw at the given position.
    */
   public static void drawTextAtXRight(double x, String text)
   {
      resolveCurrentMetafile().drawTextAtX(x, text, "right");
   }

   /**
    * Draws the text at the given X position and current Y position and left alignment.
    * 
    * @param    x
    *           The X position for text to draw in device independent units.
    * @param    text
    *           The new text to draw at the given position.
    */
   public static void drawTextAtXRight(decimal x, character text)
   {
      if (isParameterValid(x) && isParameterValid(text))
      {
         drawTextAtXRight(x.doubleValue(), text.getValue());
      }
   }

   /**
    * Draws the text between left and right X positions and current Y position with given
    * alignment.  If text does not fit the area it will be truncated until match.
    * 
    * @param    left
    *           The left X position for text to draw in device independent units.
    * @param    right
    *           The right X position for text to draw in device independent units.
    * @param    text
    *           The new text to fit and draw in the given positions.
    * @param    align
    *           The alignment value, either left or right.
    */
   public static void drawTextBetween(double left, double right, String text, String align)
   {
      resolveCurrentMetafile().drawTextBetween(left, right, text, align);
   }
   
   /**
    * Draws the text between left and right X positions and current Y position with given
    * alignment.  If text does not fit the area it will be truncated until match.
    * 
    * @param    left
    *           The left X position for text to draw in device independent units.
    * @param    right
    *           The right X position for text to draw in device independent units.
    * @param    text
    *           The new text to fit and draw in the given positions.
    * @param    align
    *           The alignment value, either left or right.
    */
   public static void drawTextBetween(decimal left, decimal right, character text, String align)
   {
      if (isParameterValid(left) && isParameterValid(right) && isParameterValid(text))
      {
         drawTextBetween(left.doubleValue(), right.doubleValue(), text.getValue(), align);
      }
   }
   
   /**
    * Draws the text between left and right X positions and current Y position with left
    * alignment.  If text does not fit the area it will be truncated until match.
    * 
    * @param    left
    *           The left X position for text to draw in device independent units.
    * @param    right
    *           The right X position for text to draw in device independent units.
    * @param    text
    *           The new text to fit and draw in the given positions.
    */
   public static void drawTextBetweenLeft(double left, double right, String text)
   {
      resolveCurrentMetafile().drawTextBetween(left, right, text, "left");
   }
   
   /**
    * Draws the text between left and right X positions and current Y position with left
    * alignment.  If text does not fit the area it will be truncated until match.
    * 
    * @param    left
    *           The left X position for text to draw in device independent units.
    * @param    right
    *           The right X position for text to draw in device independent units.
    * @param    text
    *           The new text to fit and draw in the given positions.
    */
   public static void drawTextBetweenLeft(decimal left, decimal right, character text)
   {
      if (isParameterValid(left) && isParameterValid(right) && isParameterValid(text))
      {
         drawTextBetweenLeft(left.doubleValue(), right.doubleValue(), text.getValue());
      }
   }
   
   /**
    * Draws the text between left and right X positions and current Y position with right
    * alignment.  If text does not fit the area it will be truncated until match.
    * 
    * @param    left
    *           The left X position for text to draw in device independent units.
    * @param    right
    *           The right X position for text to draw in device independent units.
    * @param    text
    *           The new text to fit and draw in the given positions.
    */
   public static void drawTextBetweenRight(double left, double right, String text)
   {
      resolveCurrentMetafile().drawTextBetween(left, right, text, "right");
   }
   
   /**
    * Draws the text between left and right X positions and current Y position with right
    * alignment.  If text does not fit the area it will be truncated until match.
    * 
    * @param    left
    *           The left X position for text to draw in device independent units.
    * @param    right
    *           The right X position for text to draw in device independent units.
    * @param    text
    *           The new text to fit and draw in the given positions.
    */
   public static void drawTextBetweenRight(decimal left, decimal right, character text)
   {
      if (isParameterValid(left) && isParameterValid(right) && isParameterValid(text))
      {
         drawTextBetweenRight(left.doubleValue(), right.doubleValue(), text.getValue());
      }
   }
   
   /**
    * Sets new values for line attributes.
    * 
    * @param    style
    *           The new String value for the current line style.  The valid style values:
    *           "solid", "dash", "dot", "dashdot", "dashdotdot", "null", "insideframe".
    * @param    thickness
    *           The thick of the line in device independent units.
    * @param    redColor
    *           The new red part of the color value.
    * @param    greenColor
    *           The new green part of the color value.
    * @param    blueColor
    *           The new blue part of the color value.
    */
   public static void setLineAttributes(String style, double thickness,
                                        int redColor, int greenColor, int blueColor)
   {
      resolveCurrentMetafile().setLineAttributes(style, thickness,
                                                 redColor, greenColor, blueColor);
   }

   /**
    * Sets new values for line attributes.
    * 
    * @param    style
    *           The new String value for the current line style.  The valid style values:
    *           "solid", "dash", "dot", "dashdot", "dashdotdot", "null", "insideframe".
    * @param    thickness
    *           The thick of the line in device independent units.
    * @param    redColor
    *           The new red part of the color value.
    * @param    greenColor
    *           The new green part of the color value.
    * @param    blueColor
    *           The new blue part of the color value.
    */
   public static void setLineAttributes(String style, decimal thickness,
                                        int redColor, int greenColor, int blueColor)
   {
      if (isParameterValid(thickness))
      {
         setLineAttributes(style, thickness.doubleValue(), redColor, greenColor, blueColor);
      }
   }

   /**
    * Sets new values for line attributes.
    * 
    * @param    style
    *           The new String value for the current line style.  The valid style values:
    *           "solid", "dash", "dot", "dashdot", "dashdotdot", "null", "insideframe".
    * @param    thickness
    *           The thick of the line in device independent units.
    * @param    redColor
    *           The new red part of the color value.
    * @param    greenColor
    *           The new green part of the color value.
    * @param    blueColor
    *           The new blue part of the color value.
    */
   public static void setLineAttributes(character style, decimal thickness,
                                        integer redColor, integer greenColor, integer blueColor)
   {
      if (isParameterValid(style) && isParameterValid(thickness) && isParameterValid(redColor) &&
          isParameterValid(greenColor) && isParameterValid(blueColor))
      {
         setLineAttributes(style.getValue(), thickness.doubleValue(), redColor.intValue(),
                           greenColor.intValue(), blueColor.intValue());
      }
   }

   /**
    * Sets new current color value to be used in line based primitives.
    * 
    * @param    rgbColor
    *           The new 32-bit color value packed in int value.
    */
   public static void setLineColor(int rgbColor)
   {
      resolveCurrentMetafile().setLineColor(rgbColor);
   }

   /**
    * Sets new current color value to be used in line based primitives.
    * 
    * @param    rgbColor
    *           The new 32-bit color value packed in int value.
    */
   public static void setLineColor(integer rgbColor)
   {
      if (isParameterValid(rgbColor))
      {
         setLineColor(rgbColor.intValue());
      }
   }

   /**
    * Sets new values for line attributes.
    * 
    * @param    style
    *           The new String value for the current line style.  The valid style values:
    *           "solid", "dash", "dot", "dashdot", "dashdotdot", "null", "insideframe".
    * @param    thickness
    *           The thick of the line in device independent units.
    */
   public static void setLineStyle(String style, double thickness)
   {
      resolveCurrentMetafile().setLineStyle(style, thickness);
   }

   /**
    * Sets new values for line attributes.
    * 
    * @param    style
    *           The new String value for the current line style.  The valid style values:
    *           "solid", "dash", "dot", "dashdot", "dashdotdot", "null", "insideframe".
    * @param    thickness
    *           The thick of the line in device independent units.
    */
   public static void setLineStyle(String style, decimal thickness)
   {
      if (isParameterValid(thickness))
      {
         setLineStyle(style, thickness.doubleValue());
      }
   }

   /**
    * Sets new values for line attributes.
    * 
    * @param    style
    *           The new String value for the current line style.  The valid style values:
    *           "solid", "dash", "dot", "dashdot", "dashdotdot", "null", "insideframe".
    * @param    thickness
    *           The thick of the line in device independent units.
    */
   public static void setLineStyle(character style, decimal thickness)
   {
      if (isParameterValid(style) && isParameterValid(thickness))
      {
         setLineStyle(style.getValue(), thickness.doubleValue());
      }
   }

   /**
    * Sets new current color value to be used in fill based primitives.
    * 
    * @param    rgbColor
    *           The new 32-bit color value packed in int value.
    */
   public static void setFillColor(int rgbColor)
   {
      resolveCurrentMetafile().setFillColor(rgbColor);
   }

   /**
    * Sets new current color value to be used in fill based primitives.
    * 
    * @param    rgbColor
    *           The new 32-bit color value packed in int value.
    */
   public static void setFillColor(integer rgbColor)
   {
      if (isParameterValid(rgbColor))
      {
         setFillColor(rgbColor.intValue());
      }
   }

   /**
    * Draws the line from left-top point to right-bottom with current line attributes.
    * The coordintes are in device independent units. 
    * 
    * @param    left
    *           The left X position of the line start.
    * @param    top
    *           The top Y position of the line start.
    * @param    right
    *           The right X position of the line start.
    * @param    bottom
    *           The bottom Y position of the line start.
    */
   public static void drawLine(double left, double top, double right, double bottom)
   {
      resolveCurrentMetafile().drawLine(left, top, right, bottom);
   }

   /**
    * Draws the line from left-top point to right-bottom with current line attributes.
    * The coordintes are in device independent units. 
    * 
    * @param    left
    *           The left X position of the line start.
    * @param    top
    *           The top Y position of the line start.
    * @param    right
    *           The right X position of the line start.
    * @param    bottom
    *           The bottom Y position of the line start.
    */
   public static void drawLine(decimal left, decimal top, decimal right, decimal bottom)
   {
      if (isParameterValid(left) && isParameterValid(top) &&
          isParameterValid(right) && isParameterValid(bottom))
      {
         drawLine(left.doubleValue(), top.doubleValue(),
                  right.doubleValue(), bottom.doubleValue());
      }
   }

   /**
    * Draws the line from left-top point to right-bottom with current line attributes.
    * The coordintes are in device independent units. 
    * 
    * @param    left
    *           The left X position of the line start.
    * @param    top
    *           The top Y position of the line start.
    * @param    right
    *           The right X position of the line start.
    * @param    bottom
    *           The bottom Y position of the line start.
    */
   public static void drawLine(double left, decimal top, decimal right, decimal bottom)
   {
      if (isParameterValid(top) && isParameterValid(right) && isParameterValid(bottom))
      {
         drawLine(left, top.doubleValue(), right.doubleValue(), bottom.doubleValue());
      }
   }

   /**
    * Draws the line from left-top point to right-bottom with current line attributes.
    * The coordintes are in device independent units. 
    * 
    * @param    left
    *           The left X position of the line start.
    * @param    top
    *           The top Y position of the line start.
    * @param    right
    *           The right X position of the line start.
    * @param    bottom
    *           The bottom Y position of the line start.
    */
   public static void drawLine(decimal left, decimal top, double right, decimal bottom)
   {
      if (isParameterValid(left) && isParameterValid(top) && isParameterValid(bottom))
      {
         drawLine(left.doubleValue(), top.doubleValue(), right, bottom.doubleValue());
      }
   }

   /**
    * Draws the line from left-top point to right-bottom with current line attributes.
    * The coordintes are in device independent units. 
    * 
    * @param    left
    *           The left X position of the line start.
    * @param    top
    *           The top Y position of the line start.
    * @param    right
    *           The right X position of the line start.
    * @param    bottom
    *           The bottom Y position of the line start.
    */
   public static void drawLine(double left, decimal top, double right, decimal bottom)
   {
      if (isParameterValid(top) && isParameterValid(bottom))
      {
         drawLine(left, top.doubleValue(), right, bottom.doubleValue());
      }
   }

   /**
    * Draws the line from left-top point to right-bottom with current line attributes.
    * The coordintes are in device independent units. 
    * 
    * @param    left
    *           The left X position of the line start.
    * @param    top
    *           The top Y position of the line start.
    * @param    right
    *           The right X position of the line start.
    * @param    bottom
    *           The bottom Y position of the line start.
    */
   public static void drawLine(decimal left, double top, double right, double bottom)
   {
      if (isParameterValid(left))
      {
         drawLine(left.doubleValue(), top, right, bottom);
      }
   }

   /**
    * Draws the line from left-top point to right-bottom with current line attributes.
    * The coordintes are in device independent units. 
    * 
    * @param    left
    *           The left X position of the line start.
    * @param    top
    *           The top Y position of the line start.
    * @param    right
    *           The right X position of the line start.
    * @param    bottom
    *           The bottom Y position of the line start.
    */
   public static void drawLine(decimal left, double top, decimal right, double bottom)
   {
      if (isParameterValid(left) && isParameterValid(right))
      {
         drawLine(left.doubleValue(), top, right.doubleValue(), bottom);
      }
   }

   /**
    * Draws the framed rectangle between left-top point and right-bottom with current line
    * attributes.  The coordintes are in device independent units. 
    * 
    * @param    left
    *           The left X position of the rectangle.
    * @param    top
    *           The top Y position of the rectangle.
    * @param    right
    *           The right X position of the rectangle.
    * @param    bottom
    *           The bottom Y position of the rectangle.
    */
   public static void drawRect(double left, double top, double right, double bottom)
   {
      resolveCurrentMetafile().drawRect(left, top, right, bottom);
   }

   /**
    * Draws the framed rectangle between left-top point and right-bottom with current line
    * attributes.  The coordintes are in device independent units. 
    * 
    * @param    left
    *           The left X position of the rectangle.
    * @param    top
    *           The top Y position of the rectangle.
    * @param    right
    *           The right X position of the rectangle.
    * @param    bottom
    *           The bottom Y position of the rectangle.
    */
   public static void drawRect(decimal left, decimal top, decimal right, decimal bottom)
   {
      if (isParameterValid(left) && isParameterValid(top) &&
          isParameterValid(right) && isParameterValid(bottom))
      {
         drawRect(left.doubleValue(), top.doubleValue(),
                  right.doubleValue(), bottom.doubleValue());
      }
   }

   /**
    * Draws the framed rectangle between left-top point and right-bottom with current line
    * attributes.  The coordintes are in device independent units. 
    * 
    * @param    left
    *           The left X position of the rectangle.
    * @param    top
    *           The top Y position of the rectangle.
    * @param    right
    *           The right X position of the rectangle.
    * @param    bottom
    *           The bottom Y position of the rectangle.
    */
   public static void drawRect(double left, decimal top, decimal right, decimal bottom)
   {
      if (isParameterValid(top) && isParameterValid(right) && isParameterValid(bottom))
      {
         drawRect(left, top.doubleValue(), right.doubleValue(), bottom.doubleValue());
      }
   }

   /**
    * Draws the rectangle between left-top point and right-bottom with current line and fill
    * attributes.  The coordintes are in device independent units. 
    * 
    * @param    left
    *           The left X position of the rectangle.
    * @param    top
    *           The top Y position of the rectangle.
    * @param    right
    *           The right X position of the rectangle.
    * @param    bottom
    *           The bottom Y position of the rectangle.
    */
   public static void drawFilledRect(double left, double top, double right, double bottom)
   {
      resolveCurrentMetafile().drawFilledRect(left, top, right, bottom);
   }

   /**
    * Draws the rectangle between left-top point and right-bottom with current line and fill
    * attributes.  The coordintes are in device independent units. 
    * 
    * @param    left
    *           The left X position of the rectangle.
    * @param    top
    *           The top Y position of the rectangle.
    * @param    right
    *           The right X position of the rectangle.
    * @param    bottom
    *           The bottom Y position of the rectangle.
    */
   public static void drawFilledRect(decimal left, decimal top, decimal right, decimal bottom)
   {
      if (isParameterValid(left) && isParameterValid(top) &&
          isParameterValid(right) && isParameterValid(bottom))
      {
         drawFilledRect(left.doubleValue(), top.doubleValue(),
                        right.doubleValue(), bottom.doubleValue());
      }
   }

   /**
    * Sets new values for image alignment.
    * 
    * @param    horizAlign
    *           The new String value for horizontal text alignment.  The valid alignment values:
    *           "left", "center", "right".
    * @param    vertAlign
    *           The new String value for vertical text alignment.  The valid alignment values:
    *           "top", "baseline", "bottom".
    */
   public static void setImageAlign(String horizAlign, String vertAlign)
   {
      resolveCurrentMetafile().setImageAlign(horizAlign, vertAlign);
   }

   /**
    * Draws the image.
    * 
    * @param    option
    *           The image painting option for example "origratio=yes".
    * @param    x
    *           The image X position.
    * @param    y
    *           The image Y position.
    * @param    width
    *           The image width in metric units.
    * @param    height
    *           The image height in metric units.
    * @param    filename
    *           The name of the file to use as image.
    * @param    imgWidth
    *           The final painted image width in metric units(return value).
    * @param    imgHeight
    *           The final painted image height in metric units(return value).
    */
   public static void drawImage(String option, double x, double y, double width, double height,
                                String filename, decimal imgWidth, decimal imgHeight)
   {
      resolveCurrentMetafile().drawImage(option, x, y, width, height, filename,
                                         imgWidth, imgHeight);
   }

   /**
    * Draws the image.
    * 
    * @param    option
    *           The image painting option for example "origratio=yes".
    * @param    x
    *           The image X position.
    * @param    y
    *           The image Y position.
    * @param    width
    *           The image width in metric units.
    * @param    height
    *           The image height in metric units.
    * @param    filename
    *           The name of the file to use as image.
    * @param    imgWidth
    *           The final painted image width in metric units(return value).
    * @param    imgHeight
    *           The final painted image height in metric units(return value).
    */
   public static void drawImage(String option, decimal x, decimal y, decimal width,
                                decimal height, character filename,
                                decimal imgWidth, decimal imgHeight)
   {
      if (isParameterValid(x) && isParameterValid(y) && isParameterValid(width) &&
          isParameterValid(height) && isParameterValid(filename))
      {
         drawImage(option, x.doubleValue(), y.doubleValue(), width.doubleValue(),
                   height.doubleValue(), filename.getValue(), imgWidth, imgHeight);
      }
   }

   /**
    * Draws the image.
    * 
    * @param    option
    *           The image painting option for example "origratio=yes".
    * @param    x
    *           The image X position.
    * @param    y
    *           The image Y position.
    * @param    width
    *           The image width in metric units.
    * @param    height
    *           The image height in metric units.
    * @param    filename
    *           The name of the file to use as image.
    * @param    imgWidth
    *           The final painted image width in metric units(return value).
    * @param    imgHeight
    *           The final painted image height in metric units(return value).
    */
   public static void drawImage(character option, decimal x, decimal y, decimal width,
                                decimal height, character filename,
                                decimal imgWidth, decimal imgHeight)
   {
      if (isParameterValid(option) && isParameterValid(x) && isParameterValid(y) &&
           isParameterValid(width) && isParameterValid(height) && isParameterValid(filename))
      {
         drawImage(option.getValue(), x.doubleValue(), y.doubleValue(), width.doubleValue(),
                   height.doubleValue(), filename.getValue(), imgWidth, imgHeight);
      }
   }

   /**
    * Draws the image.
    * 
    * @param    option
    *           The image painting option for example "origratio=yes".
    * @param    x
    *           The image X position.
    * @param    y
    *           The image Y position.
    * @param    width
    *           The image width in metric units.
    * @param    height
    *           The image height in metric units.
    * @param    filename
    *           The name of the file to use as image.
    * @param    imgWidth
    *           The final painted image width in metric units(return value).
    * @param    imgHeight
    *           The final painted image height in metric units(return value).
    */
   public static void drawImage(String option, decimal x, decimal y, double width, double height,
                                character filename, decimal imgWidth, decimal imgHeight)
   {
      if (isParameterValid(x) && isParameterValid(y) && isParameterValid(filename))
      {
         drawImage(option, x.doubleValue(), y.doubleValue(), width, height, filename.getValue(),
                   imgWidth, imgHeight);
      }
   }

   /**
    * Draws the part of the image.
    * 
    * @param    x
    *           The image X position.
    * @param    y
    *           The image Y position.
    * @param    widthPix
    *           The image width in pixels.
    * @param    heightPix
    *           The image height in pixels.
    * @param    maxWidthCm
    *           The image maximum right border not to exceed.
    * @param    filename
    *           The name of the file to use as image.
    */
   public static void drawImagePart(double x, double y, int widthPix, int heightPix,
                                    double maxWidthCm, String filename)
   {
      resolveCurrentMetafile().drawImagePart(x, y, widthPix, heightPix, maxWidthCm, filename);
   }

   /**
    * Draws the part of the image.
    * 
    * @param    x
    *           The image X position.
    * @param    y
    *           The image Y position.
    * @param    widthPix
    *           The image width in pixels.
    * @param    heightPix
    *           The image height in pixels.
    * @param    maxWidthCm
    *           The image maximum right border not to exceed.
    * @param    filename
    *           The name of the file to use as image.
    */
   public static void drawImagePart(decimal x, decimal y, integer widthPix, integer heightPix,
                                    decimal maxWidthCm, character filename)
   {
      if (isParameterValid(x) && isParameterValid(y) && isParameterValid(widthPix) &&
          isParameterValid(heightPix) && isParameterValid(maxWidthCm) &&
          isParameterValid(filename))
      {
         drawImagePart(x.doubleValue(), y.doubleValue(), widthPix.intValue(),
                       heightPix.intValue(), maxWidthCm.doubleValue(), filename.getValue());
      }
   }

   /**
    * Sets new value for current page orientation.
    * 
    * @param    mode
    *           The new String value for page orientation.  The valid alignment values:
    *           "standard", "portrait", "landscape".
    */
   public static void setPageOrientation(character mode)
   {
      if (isParameterValid(mode))
      {
         resolveCurrentMetafile().setPageOrientation(mode.getValue());
      }
   }
   
   /**
    * Sets new value for current page orientation.
    * 
    * @param    mode
    *           The new String value for page orientation.  The valid alignment values:
    *           "standard", "portrait", "landscape".
    */
   public static void setPageOrientation(String mode)
   {
      resolveCurrentMetafile().setPageOrientation(mode);
   }
   
   /**
    * Gets the current page width.
    * 
    * @param    pageWidth
    *           The current page width value in device independent units.
    */
   public static void getPageWidth(decimal pageWidth)
   {
      resolveCurrentMetafile().getPageWidth(pageWidth);
   }

   /**
    * Gets the current page height.
    * 
    * @param    pageHeight
    *           The current page height value in device independent units.
    */
   public static void getPageHeight(decimal pageHeight)
   {
      resolveCurrentMetafile().getPageHeight(pageHeight);
   }

   /**
    * Sets new value for current/maximum page number.
    * 
    * @param    mode
    *           The new String value for page mode.  The valid values: "append", "new".
    * @param    newMaxPageNum
    *           The integer max page number value.
    */
   public static void resetPage(String mode, int newMaxPageNum)
   {
      resolveCurrentMetafile().resetPage(mode, newMaxPageNum);
   }
   
   /**
    * Sets new value for current/maximum page number.
    * 
    * @param    mode
    *           The new String value for page mode.  The valid values: "append", "new".
    * @param    newMaxPageNum
    *           The integer max page number value.
    */
   public static void resetPage(String mode, integer newMaxPageNum)
   {
      if (isParameterValid(newMaxPageNum))
      {
         resetPage(mode, newMaxPageNum.intValue());
      }
   }
   
   /**
    * Gets the current page number.
    * 
    * @param    pageNum
    *           The current integer page number value.
    */
   public static void getPageNumber(integer pageNum)
   {
      resolveCurrentMetafile().getPageNumber(pageNum);
   }

   /**
    * Sets new value for current page number.
    * 
    * @param    newPageNum
    *           The integer page number value.
    */
   public static void setPageNumber(int newPageNum)
   {
      resolveCurrentMetafile().setPageNumber(newPageNum);
   }

   /**
    * Sets the new current position for page number.  Can be used to draw page number text for
    * header and footer.
    * 
    * @param    x
    *           The new X position for page nummber.
    * @param    y
    *           The new Y position for page nummber.
    */
   public static void setPageNumberPosition(double x, double y)
   {
      resolveCurrentMetafile().setPageNumberPosition(x, y);
   }

   /**
    * Sets the new current position for page number.  Can be used to draw page number text for
    * header and footer.
    * 
    * @param    x
    *           The new X position for page nummber.
    * @param    y
    *           The new Y position for page nummber.
    */
   public static void setPageNumberPosition(decimal x, decimal y)
   {
      if (isParameterValid(x) && isParameterValid(y))
      {
         setPageNumberPosition(x.doubleValue(), y.doubleValue());
      }
   }

   /**
    * Sets new value for current page number text.
    * 
    * @param    text
    *           The new text for page number.
    * @param    pgNumberNdx
    *           The index of new text for page number.
    */
   public static void setPageNumberText(String text, int pgNumberNdx)
   {
      resolveCurrentMetafile().setPageNumberText(text, pgNumberNdx);
   }

   /**
    * Sets new value for current page number text.
    * 
    * @param    text
    *           The new text for page number.
    * @param    pgNumberNdx
    *           The index of new text for page number.
    */
   public static void setPageNumberText(character text, int pgNumberNdx)
   {
      if (isParameterValid(text))
      {
         setPageNumberText(text.getValue(), pgNumberNdx);
      }
   }

   /**
    * Sets new value for current page number text(alternative version).
    * 
    * @param    x
    *           The new X position for page nummber.
    * @param    y
    *           The new Y position for page nummber.
    * @param    text
    *           The new text for page number.
    */
   public static void setPageNumberText(double x, double y, String text)
   {
      resolveCurrentMetafile().setPageNumberPosition(x, y);
      resolveCurrentMetafile().setPageNumberText(text, 1);
   }

   /**
    * Sets new value for current page number text(alternative version).
    * 
    * @param    x
    *           The new X position for page nummber.
    * @param    y
    *           The new Y position for page nummber.
    * @param    text
    *           The new text for page number.
    */
   public static void setPageNumberText(decimal x, decimal y, character text)
   {
      if (isParameterValid(x) && isParameterValid(y) && isParameterValid(text))
      {
         setPageNumberText(x.doubleValue(), y.doubleValue(), text.getValue());
      }
   }

   /**
    * Sets to show page footer or not.
    * 
    * @param    show
    *           The <code>1</code> value means show footer, <code>0</code> means no footer.
    */
   public static void showPageFooter(int show)
   {
      resolveCurrentMetafile().showPageFooter(show == 1);
   }

   /**
    * Sets to show page footer or not.
    * 
    * @param    show
    *           The <code>1</code> value means show footer, <code>0</code> means no footer.
    */
   public static void showPageFooter(integer show)
   {
      if (isParameterValid(show))
      {
         showPageFooter(show.intValue());
      }
   }

   /**
    * General metafile subsystem init procedure.
    * 
    * @param    footerTextHeight
    *           The height of the footed text in device independent units.
    * @param    tmpDir
    *           The temporary directory to use.
    * @param    ok
    *           The return value, meaning success init or not.
    */
   public static void initialize(double footerTextHeight, String tmpDir, integer ok)
   {
      resolveCurrentMetafile().initialize(footerTextHeight, tmpDir, ok);
   }

   /**
    * General metafile subsystem init procedure.
    * 
    * @param    footerTextHeight
    *           The height of the footed text in device independent units.
    * @param    tmpDir
    *           The temporary directory to use.
    * @param    ok
    *           The return value, meaning success init or not.
    */
   public static void initialize(decimal footerTextHeight, character tmpDir, integer ok)
   {
      if (isParameterValid(footerTextHeight) && isParameterValid(tmpDir))
      {
         initialize(footerTextHeight.doubleValue(), tmpDir.getValue(), ok);
      }
   }

   /**
    * Gets optionally set temporary directory to store generated PDF temporary files.
    * 
    * @return   The currently used temporary directory.
    */
   public static String getTmpDirectory()
   {
      return resolveCurrentMetafile().getTmpDirectory();
   }

   /**
    * Makes PDF file from the currently recorded metafile.
    * 
    * @param    pdfFileName
    *           The name of the PDF file to create.
    */
   public static void makePdf(String pdfFileName)
   {
      // get the metafile object
      WorkArea wa = work.obtain();
      
      // intercept Jasper related issues tp avoid client abend too
      try
      {
         // ensure all pending objects are committed
         wa.currentMf.stopRecording();
      
         // time to create jasper related worker
         Xpr2PdfWorker pdfOutput = new Xpr2PdfWorker(wa.currentMf, pdfFileName);
         // export to PDF
         if (!pdfOutput.export(false))
         {
            XprHelper.displayOrLogError(String.format("MetafileHelper export failed for %s.",
                                                      pdfFileName));
         }
      }
      catch (Exception e)
      {
         XprHelper.displayOrLogError(String.format(
            "MetafileHelper has faced unexpected problem with report generation for PDF file %s.",
             pdfFileName), e);
      }
         
      // after pdf was made we need to clean up current session based metafile
      wa.currentMf = null;
      
      // as final step we load file into client context
      if(openFile) 
      {
         WebBrowserManager.openMimeResource(XprHelper.PDF_MIME,
                                            XprHelper.FILE_PROTOCOL + pdfFileName, false);
      }
   }
   
   /**
    * Makes PDF file from the currently recorded metafile.
    * 
    * @param    pdfFileName
    *           The name of the PDF file to create.
    */
   public static void makePdf(character pdfFileName)
   {
      if (isParameterValid(pdfFileName))
      {
         makePdf(pdfFileName.getValue());
      }
   }
   
   /**
    * Makes PDF file from the currently recorded metafile with preview mode or not.
    * 
    * @param    pdfFileName
    *           The name of the PDF file to create.
    * @param    openFilePDF
    *           preview prevents opening the PDF file instantly
    */
   public static void makePdf(character pdfFileName, boolean openFilePDF)
   {
      if (isParameterValid(pdfFileName))
      {
         makePdf(pdfFileName.getValue(), openFilePDF);
      }
   }
   
   /**
    * Makes PDF file from the currently recorded metafile with preview mode or not.
    * 
    * @param    pdfFileName
    *           The name of the PDF file to create.
    * @param    openFilePDF
    *           preview prevents opening the PDF file instantly
    */
   public static void makePdf(String pdfFileName, logical openFilePDF)
   {
      if (isParameterValid(openFilePDF))
      {
         makePdf(pdfFileName, openFilePDF.booleanValue());
      }
   }
   
   /**
    * Makes PDF file from the currently recorded metafile with preview mode or not.
    * 
    * @param    pdfFileName
    *           The name of the PDF file to create.
    * @param    openFilePDF
    *           preview prevents opening the PDF file instantly
    */
   public static void makePdf(character pdfFileName, logical openFilePDF)
   {
      if (isParameterValid(pdfFileName) && isParameterValid(openFilePDF))
      {
         makePdf(pdfFileName.getValue(), openFilePDF.booleanValue());
      }
   }
   
   /**
    * Makes PDF file from the currently recorded metafile with preview mode or not.
    * 
    * @param    pdfFileName
    *           The name of the PDF file to create.
    * @param    openFilePDF
    *           preview prevents opening the PDF file instantly
    */
   public static void makePdf(String pdfFileName, boolean openFilePDF)
   {
      try
      {
         openFile = openFilePDF;
         makePdf(pdfFileName);
      }
      finally
      {
         openFile = true;
      }
   }

   /**
    * Stops all operation for the current metafile.
    */
   public static void stopRecording()
   {
      resolveCurrentMetafile().stopRecording();
   }

   /**
    * Interrupts all operation for the current metafile.
    */
   public static void interruptRecording()
   {
      work.obtain().currentMf = null;
   }

   /**
    * Converts X cm metafile coordinate to XPR column value.
    * 
    * @param    x
    *           The X coordinate to transform.
    * 
    * @return   The column value to use in JasperReports XPR objects handling.
    */
   public static double x2Col(double x)
   {
      // gets the pixel value of the metafile coord and converts to XPR column
      return (double)mfUnits2pix(x) / pixPerCol; 
   }
   
   /**
    * Converts Y cm metafile coordinate to XPR row value.
    * 
    * @param    y
    *           The Y coordinate to transform.
    * 
    * @return   The row value to use in JasperReports XPR objects handling.
    */
   public static double y2Row(double y)
   {
      // gets the pixel value of the metafile coord and converts to XPR row
      return (double)mfUnits2pix(y) / pixPerRow;
   }
   
   /**
    * Converts pixel coordinate value into metafile device independent unit value.
    * 
    * @param    pixelValue
    *           The coordinate in pixels to transform.
    * @param    mfUnits
    *           The device independent unit, can be inch/cm/mm.
    */
   public static void pix2MfUnits(int pixelValue, decimal mfUnits)
   {
      double val = pix2MfUnits(pixelValue);
      
      mfUnits.assign(new decimal(val));
   }
   
   /**
    * Converts pixel coordinate value into metafile device independent unit value.
    * 
    * @param    pixelValue
    *           The coordinate in pixels to transform.
    * @param    mfUnits
    *           The device independent unit, can be inch/cm/mm.
    */
   public static void pix2MfUnits(integer pixelValue, decimal mfUnits)
   {
      if (isParameterValid(pixelValue))
      {
         pix2MfUnits(pixelValue.intValue(), mfUnits);
      }
   }
   
   /**
    * Converts pixel coordinate value into metafile device independent unit value.
    * 
    * @param    pixelValue
    *           The coordinate in pixels to transform.
    * 
    * @return   The device independent unit, can be inch/cm/mm.
    */
   public static double pix2MfUnits(float pixelValue)
   {
      // defaulting to inches
      double res = (double)pixelValue / (double) (Xpr2PdfWorker.DPI_DEFAULT);
      
      // can be CM or MM as well
      if (units == UNITS_MM)
      {
         res *= XprEntity.INCH_2_MM;
      }
      else if (units == UNITS_CM)
      {
         res *= XprEntity.INCH_2_MM / 10.0;
      }
      
      return res;
   }
   
   /**
    * Converts metafile coordinate value into pixel based coordinate.
    * 
    * @param    mfValue
    *           The coordinate in metafile units to transform.
    * 
    * @return   The coordinale in pixels.
    */
   public static int mfUnits2pix(double mfValue)
   {
      // defaulting to use inches
      double res = (double)mfValue * (double) (Xpr2PdfWorker.DPI_DEFAULT);
      
      // can be CM or MM as well
      if (units == UNITS_MM)
      {
         res /= XprEntity.INCH_2_MM;
      }
      else if (units == UNITS_CM)
      {
         res /= XprEntity.INCH_2_MM / 10.0;
      }
      
      return (int)Math.round(res);
   }
   
   /**
    * Checks if the given parameter is valid and can be used in Metafile api.
    * 
    * @param    op
    *           The parameter to test.
    *
    * @return   <code>TRUE</code> if valid, <code>FALSE</code> otherwise.
    */
   public static boolean isParameterValid(BaseDataType op)
   {
      return op != null && !op.isUnknown();
   }
   
   /**
    * Calculates the scaling factor for given text.  Different letters have different width for variable
    * size fonts like Arial.  We need to compensate this difference to get proper text width.
    * 
    * @param    textToTest
    *           The text to measure letters scale.
    * @param    fontName
    *           The font to be used to draw the text.
    * @param    isBold
    *           If the font to be used is bold or not.
    * @param    isItalic
    *           If the font to be used is italic or not.
    *
    * @return   The scaling for given string to calculate text width.
    */
   public static double getTextWeightScale(String textToTest, String fontName,
                                           boolean isBold, boolean isItalic)
   {
      double res = 0.0;

      // some simple NPE and errors protection
      // fixed width font can also be exculded
      if (textToTest == null || textToTest.isEmpty() || fontName.equals("Courier"))
      {
         return 1.0;
      }
      
      // text length
      int length = textToTest.length();
      for (int i = 0; i < length; i++)
      {
         char chNext = textToTest.charAt(i);
         switch (chNext)
         {
            case ' ':
            case ':':
            case '.':
            case ',':
            case ';':
            case '[':
            case ']':
            case '(':
            case ')':
            case '!':
            case '\'':
            case '|':
            {
               // very narrow letters
               res += 0.555;
               break;
            }
            case 'I':
            case 'i':
            case 'L':
            case 'l':
            case '/':
            case '\\':
            case '-':
            case '*':
            case 'f':
            case 't':
            case '{':
            case '}':
            {
               // narrow letters
               res += 0.75;
               break;
            }
            case 'Z':
            case 'M':
            case 'G':
            case 'E':
            case 'R':
            case 'O':
            case 'D':
            case 'N':
            case 'S':
            {
               // very wide letters
               res += 1.35;
               break;
            }
            case 'W':
            case '@':
            case '%':
            {
               // extra wide letters
               res += 1.55;
               break;
            }
            case 'A':
            case 'V':
            case 'P':
            case 'C':
            case 'w':
            case 'Q':
            {
               // wide letters
               res += 1.15;
               break;
            }
            default:
            {
               // all other letters are regular
               res += 1.0;
               break;
            }
         }
         // bold and possibly italic fonts need additional corrections in certain cases
         if (isBold && !Character.isDigit(chNext))
         {
            res += 0.1;
         }
         else if (isItalic)
         {
            res += 0.0;
         }
      }
      // normalize the scale factor
      res /= (double)length;
      
      // the Arial font needs to be a bit smaller comparing to all others
      return fontName.equals("Arial") ? res * 0.85 : res;
   }

   /**
    * Gets the instance of the current metafile to process.
    * 
    * @return   The current metafile.
    */
   private static Metafile resolveCurrentMetafile()
   {
      WorkArea wa = work.obtain();
      
      if (wa.currentMf == null)
      {
         wa.currentMf = new Metafile();
      }
      
      return wa.currentMf;
   }

   /** 
    * Stores global data relating to the state of the current context.
    */
   private static class WorkArea
   {
      /** Current per session context metafile instance. */
      private Metafile currentMf = null;
   }
   
   /**
    * Simple container that stores and returns a context-local instance of
    * the global work area.
    */
   private static class ContextContainer
   extends ContextLocal<WorkArea>
   {
      /**
       * Obtains the context-local instance of the contained global work
       * area.
       *
       * @return   The work area associated with this context.
       */
      public WorkArea obtain() 
      {
         return this.get();
      }   
      
      /**
       * Initializes the work area, the first time it is requested within a
       * new context.
       *
       * @return   The newly instantiated work area.
       */
      protected synchronized WorkArea initialValue()
      {
         WorkArea wa = new WorkArea();
         
         return wa;
      }   
   }   
}