Metafile.java
/*
** Module : Metafile.java
** Abstract : Class to provide GUI metafile service.
**
** Copyright (c) 2019-2022, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------Description----------------------------------
** 001 EVL 20190514 Created initial version.
** 002 EVL 20190617 Improving line style/width implementation.
** 003 EVL 20210107 Many bug fixing and improvements.
** EVL 20210108 Completed approach for page footer generation. Added more fixes and improvements to
** clean up some cosmetic deviations.
** EVL 20210111 Continue fixup for cosmetic issues.
** EVL 20210113 Resolution for underlined text issues and improvements for text width calculation.
** Several fixes for filled rectangle painting and adding coordinates validity check.
** EVL 20210114 Fix for text height and space between lines mismatches. Improved line thick setup.
** EVL 20211001 Fix for page size bug after changing page orientation. Also improved startNewPage()
** to consider the first page internals are creating in class constructor and new page
** start is optional call that never be executed in some conditions.
** EVL 20211021 Fixed bug with draw text between left and right with right alignment.
** EVL 20211026 Changing text font size type to float.
** EVL 20211119 Small improvements for text layout.
** EVL 20211123 Reworked approach for handling text with 0.0 both position for left and right margins.
** Now this case is considering as instruction to pring nothing.
** EVL 20211129 Small improvements for text layout and underlined text rendering.
** EVL 20220126 Improvements for rendering text that has more width that rectangle allocated for text
** area. Other minor fixes with footer location.
** EVL 20220127 Improvements for text region match calculations. Added method to get underlined flag
** value for external classes.
** EVL 20220225 Fix for bold font width calculation. Need tointroduce additional scale factor for bold
** and probably italic fonts (made place-holder with 1.0 value).
** EVL 20220302 Moving handling of bold and italic text width to MetafileHelper call to ensure more
** granularity and precision.
*/
/*
** 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.util.*;
import com.goldencode.util.*;
import net.sf.jasperreports.engine.type.*;
import java.util.*;
/**
* The class to provide single GDI metafile service to finally compile and show the PDF document.
* This replacement is a platform neutral. The device independent units are centimeters for now.
*/
public class Metafile
implements PdfProvider
{
/** Left image alignment. */
public static final int IMAGE_ALIGN_LEFT = 0;
/** Center image alignment. */
public static final int IMAGE_ALIGN_CENTER = 1;
/** Right image alignment. */
public static final int IMAGE_ALIGN_RIGHT = 2;
/** Top image alignment. */
public static final int IMAGE_ALIGN_TOP = 3;
/** Bottom image alignment. */
public static final int IMAGE_ALIGN_BOTTOM = 4;
/** Left text alignment. */
public static final int TEXT_ALIGN_LEFT = 0;
/** Right text alignment. */
public static final int TEXT_ALIGN_RIGHT = 1;
/** Center text alignment. */
public static final int TEXT_ALIGN_CENTER = 2;
/** Top text alignment. */
public static final int TEXT_ALIGN_TOP = 3;
/** Baseline text alignment. */
public static final int TEXT_ALIGN_BASELINE = 4;
/** Bottom text alignment. */
public static final int TEXT_ALIGN_BOTTOM = 5;
/** Font size default value for metafile. */
private static final int FONT_SIZE_DEFAULT = 12;
/** Font name default value for metafile. */
private static final String FONT_NAME_DEFAULT = "Courier";
/** Zoom type default value for metafile. */
private static final String ZOOM_TYPE_DEFAULT = "page";
/** Optional scaling from PDF coord to metafile. */
private static final double pdf2MfDpi = 96.0 / (double)Xpr2PdfWorker.DPI_DEFAULT;
/** To convert columns to metafile pixels */
private static final double pixPerColMf = (double)Xpr2PdfWorker.DPI_DEFAULT /
(double)XprEntity.CPI_DEFAULT;
/** Constant vertical shift for object. Text, line, image and rectangle are involved. */
private static final double OBJ_Y_SHIFT = 0.45;
/** If X coord is on the lefd edge (0.0), the value must be increased. */
private static final double EDGE_LINE_X_SHIFT = 0.125;
/** Vertical lines should be shifted with this value. */
private static final double VERT_LINE_Y_SHIFT = 0.01;
/** The Z-ordered list of the objects recognized inside current file entity, paginated. */
private List<List<XprObjBase>> pages = new ArrayList<List<XprObjBase>>();
/** New page specific options. */
private List<XprPageOptions> pageOptions = new ArrayList<XprPageOptions>();
/** The objects for the current page. */
private List<XprObjBase> objects = new ArrayList<XprObjBase>();
/** The number of the current page. */
private int currPageNum = 1;
/** The number of the maximum page. */
private int maxPageNum = currPageNum;
/** Current page orientation. */
private int pageOrientation = XprPageOptions.PORTRAIT;
/** Current font name (default is 'Courier New'). */
private String currFontName = FONT_NAME_DEFAULT;
/** Current zoom type name (default is whole page. */
private String currZoomType = ZOOM_TYPE_DEFAULT;
/** Current font size (default is 12). */
private float fontSize = FONT_SIZE_DEFAULT;
/** Current font size in metric units (default is 12). */
private double fontSizeMetric = MetafileHelper.pix2MfUnits(fontSize);
/** Flag indicating if the font is currently bold or not. */
private boolean isBold = false;
/** Flag indicating if the font is currently italic or not. */
private boolean isItalic = false;
/** Flag indicating if the font is currently underlined or not. */
private boolean isUnderline = false;
/** Foreground color for text.*/
private int textColor = 0xFF000000;
/** Line color for lines, rectangles and border of the filled rectangles.*/
private int lineColor = 0xFF000000;
/** Background color for filled rectangles.*/
private int fillColor = 0xFFFFFFFF;
/** Current X value for page number text. */
private double xPageNum = 0.0;
/** Current Y value for page number text. */
private double yPageNum = 0.0;
/** Page number texts. */
private List<XprObjText> pageNumberTextObj = new ArrayList<XprObjText>();
/** Left margin value. */
private double leftMargin = 0.5;
/** Top margin value. */
private double topMargin = 0.0;
/** Footer reserverd size value. */
private double footerHeight = 0.0;
/** Extra vertical distance between two sequential rows of the text. */
private double extraRowDistance = 0.06125;
/** Current page width in device independent units. */
private double diPageWidth = 0.0;
/** Current page height in device independent units. */
private double diPageHeight = 0.0;
/** Current X value in new entity. */
private double xCurr = 0.0;
/** Current Y value in new entity. */
private double yCurr = 0.0;
/** Current line thickness value. */
private int lineWidth = 1;
/** Current horizontal text alignment. */
private int textAlignHorizCurr = TEXT_ALIGN_LEFT;
/** Current vertical text alignment. */
private int textAlignVertCurr = TEXT_ALIGN_TOP;
/** Current horizontal image alignment. */
private int imageAlignHorizCurr = IMAGE_ALIGN_LEFT;
/** Current vertical image alignment. */
private int imageAlignVertCurr = IMAGE_ALIGN_TOP;
/** Flag indicating if the footer will be shown or not. */
private boolean showFooter = false;
/** Current line style value. */
private LineStyleEnum lineStyle = LineStyleEnum.SOLID;
/** Flag indicating there are possibly records to commit. */
private boolean recordingStopped = false;
/** Temporary directory to store the intermediate files. */
private String pdfDir = null;
/** Flag to indicate the first page already created. */
private boolean firstPageCreated = false;
/**
* Default metafile object constructor.
*/
public Metafile()
{
// and create new object room for next page objects
objects = new ArrayList<XprObjBase>();
// new page options set with current default page size
setPageOptions();
// we have the first empty page now
firstPageCreated = true;
}
/**
* 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 void setTextAlignment(String horizAlign, String vertAlign)
{
// horizontal alignment type
if (horizAlign != null && !horizAlign.isEmpty())
{
horizAlign = horizAlign.toLowerCase();
if (horizAlign.equals("left"))
{
textAlignHorizCurr = TEXT_ALIGN_LEFT;
}
else if (horizAlign.equals("center"))
{
textAlignHorizCurr = TEXT_ALIGN_CENTER;
}
else if (horizAlign.equals("right"))
{
textAlignHorizCurr = TEXT_ALIGN_RIGHT;
}
}
// vetical alignment type
if (vertAlign != null && !vertAlign.isEmpty())
{
vertAlign = vertAlign.toLowerCase();
if (vertAlign.equals("top"))
{
textAlignVertCurr = TEXT_ALIGN_TOP;
}
else if (vertAlign.equals("baseline"))
{
textAlignVertCurr = TEXT_ALIGN_BASELINE;
}
else if (vertAlign.equals("bottom"))
{
textAlignVertCurr = TEXT_ALIGN_BOTTOM;
}
}
}
/**
* Sets new current color value to be used in text primitives.
*
* @param rgbColor
* The new 32-bit color value packed in int value.
*/
public void setTextColor(int rgbColor)
{
textColor = rgbColor;
}
/**
* 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 void setTextStyle(String style)
{
// simple protection from empty style value
if (style == null || style.isEmpty())
{
return;
}
// update current text style
style = style.toLowerCase();
if (style.equals("italic"))
{
isItalic = true;
}
else if (style.equals("no-italic"))
{
isItalic = false;
}
else if (style.equals("bold"))
{
isBold = true;
}
else if (style.equals("no-bold"))
{
isBold = false;
}
else if (style.equals("underline"))
{
isUnderline = true;
}
else if (style.equals("no-underline"))
{
isUnderline = false;
}
}
/**
* Gets flag meaning underlined text should be currently used.
*
* @return <code>TRUE</code> if text underlined, <code>FALSE</code> otherwise.
*/
public boolean isUnderline()
{
return isUnderline;
}
/**
* Starts the new page construction for the document that is currently processing.
*/
public void startNewPage()
{
// add objects of the current page to the page list
if (currPageNum > 1 || objects.size() > 0)
{
pages.add(objects);
}
// the first page internals might already been created in constructor
if (firstPageCreated)
{
firstPageCreated = false;
}
else
{
// start recording new page
currPageNum++;
// and create new object room for next page objects
objects = new ArrayList<XprObjBase>();
// adjust max page number too
if (currPageNum > maxPageNum)
{
maxPageNum++;
}
// new page options set with current default page size
setPageOptions();
}
}
/**
* Sets new value for current font. The font name string can have size and style in addition
* to the font name itself. In this case the character "_" is used as delimiter.
*
* @param fontName
* The new String value for the current font name. Can be "default" for default
* font to use.
*/
public void setFont(String fontName)
{
// simple protection from empty font name value
if (fontName == null || fontName.isEmpty())
{
// nothing to change
return;
}
// normalize the font names to use with PDF font mapper
if (fontName.startsWith("Times "))
{
currFontName = "Times";
}
else if (fontName.startsWith("Courier ") || fontName.equals("default"))
{
currFontName = "Courier";
}
else
{
currFontName = fontName;
}
}
/**
* Resets new value for text style to be off.
*/
public void resetTextStyle()
{
isItalic = false;
isBold = false;
isUnderline = false;
}
/**
* Gets the current font value.
*
* @param fontName
* The value for the font name currently in use.
*/
public void getFont(character fontName)
{
fontName.assign(new character(currFontName));
}
/**
* Sets new value for current zoom factor.
*
* @param zoomType
* The new String value for the current zoom type.
*/
public void setZoomFactor(String zoomType)
{
if (zoomType != null && !zoomType.isEmpty())
{
zoomType = zoomType.toLowerCase();
}
currZoomType = zoomType;
}
/**
* Gets the current zoom factor value.
*
* @param zoomType
* The value for the zoom type currently in use.
*/
public void getZoomFactor(character zoomType)
{
zoomType.assign(new character(currZoomType));
}
/**
* Gets the current font height value.
*
* @param fontHeight
* The currently used font height value in metric units.
*/
public void getFontHeight(decimal fontHeight)
{
fontHeight.assign(new decimal(fontSizeMetric));
}
/**
* Gets the current font height value.
*
* @return The currently used font height value in metric units.
*/
public double getFontHeight()
{
return fontSizeMetric;
}
/**
* Sets the current font height value.
*
* @param fontHeight
* The font height value in pics to use.
*/
public void setFontHeight(float fontHeight)
{
fontSize = (float)((double)fontHeight / pdf2MfDpi);
fontSizeMetric = MetafileHelper.pix2MfUnits(fontSize);
}
/**
* Gets the given text width value.
*
* @param textToMeasure
* The text to calculate width.
* @param textWidth
* The width of the text in metric units.
*/
public void getTextWidth(String textToMeasure, decimal textWidth)
{
double res = getTextWidth(textToMeasure);
textWidth.assign(new decimal(res));
}
/**
* Gets the given text width value.
*
* @param textToMeasure
* The text to calculate width.
*
* @return The width of the text in metric units.
*/
public double getTextWidth(String textToMeasure)
{
return getTextWidth(textToMeasure, -1, isBold, isItalic);
}
/**
* Gets the given text width value. Custom font size is taking into account.
*
* @param textToMeasure
* The text to calculate width.
* @param customFontSize
* The optional font size to use instead of current one.
* @param bold
* The bold font in use to measure text.
* @param italic
* The italic font in use to measure text.
*
* @return The width of the text in metric units.
*/
public double getTextWidth(String textToMeasure, float customFontSize, boolean bold, boolean italic)
{
double res = 0.0;
boolean customFontUsage = false;
// TODO: this can be inaccurate calculaton, might need to be re-worked
if (textToMeasure != null && !textToMeasure.isEmpty())
{
if (customFontSize > 0)
{
customFontSize = customFontSize / (float)pdf2MfDpi;
customFontUsage = true;
}
else
{
customFontSize = fontSize;
}
int length = textToMeasure.length();
res = MetafileHelper.pix2MfUnits((float)length * (float)pixPerColMf * (float)customFontSize /
(float)(FONT_SIZE_DEFAULT));
}
return customFontUsage ? res * 0.95
: res * MetafileHelper.getTextWeightScale(textToMeasure, currFontName,
bold, italic);
}
/**
* 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 void setXY(double x, double y)
{
xCurr = x;
yCurr = y;
}
/**
* 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 void getXY(decimal x, decimal y)
{
x.assign(new decimal(xCurr));
y.assign(new decimal(yCurr));
}
/**
* Sets the new value for document's left margin.
*
* @param leftMargin
* The new left margin value.
*/
public void setLeftMargin(double leftMargin)
{
this.leftMargin = leftMargin;
}
/**
* 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 void startNewTextLine()
{
xCurr = leftMargin;
yCurr += getFontHeight() + extraRowDistance;
}
/**
* Sets the extra vertical space between two sequential rows.
*
* @param extraYSpace
* The new extra Y distance between rows.
*/
public void setInterleaving(double extraYSpace)
{
extraRowDistance = extraYSpace;
}
/**
* Gets the free space below the current Y position.
*
* @param freeSpaceDown
* The remaining vertical free space on the current page.
*/
public void getFreeSpaceDown(decimal freeSpaceDown)
{
double val = diPageHeight - yCurr - footerHeight;
freeSpaceDown.assign(new decimal(val));
}
/**
* Gets the free space to the right the current X position.
*
* @param freeSpaceRight
* The remaining horizontal free space on the current line(page).
*/
public void getFreeSpaceRight(decimal freeSpaceRight)
{
double val = diPageWidth - xCurr;
freeSpaceRight.assign(new decimal(val));
}
/**
* Draws the text at current position.
*
* @param text
* The new text to draw at current position.
*/
public void drawText(String text)
{
double x = xCurr;
// consider current alignment and adjust X position
switch (textAlignHorizCurr)
{
case TEXT_ALIGN_RIGHT:
{
x -= getTextWidth(text);
// shift the current X position to the end of the text
break;
}
case TEXT_ALIGN_CENTER:
{
x -= getTextWidth(text) / 2.0;
// shift the current X position to the end of the text
break;
}
}
if (!text.isEmpty())
{
addTextObject(text, x, yCurr);
// shift the current X position to the end of the text
xCurr += getTextWidth(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 void drawTextAndSetX(double x, String text)
{
drawText(text);
xCurr = Math.min(xCurr, x);
}
/**
* 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 void drawTextAtX(double x, String text, String align)
{
// simple protection from empty style value
if (align == null || align.isEmpty())
{
return;
}
// currently used text alignment
int xTextAlign = textAlignHorizCurr;
// check if we need to override text alignment
align = align.toLowerCase();
if (align.equals("left"))
{
xTextAlign = TEXT_ALIGN_LEFT;
}
else if (align.equals("right"))
{
xTextAlign = TEXT_ALIGN_RIGHT;
}
// consider current alignment and adjust X position
switch (xTextAlign)
{
case TEXT_ALIGN_LEFT:
{
// shift the current X position to the end of the text
xCurr = x + getTextWidth(text);
break;
}
case TEXT_ALIGN_RIGHT:
{
// the current X position become now the text right point
xCurr = x;
// shift the current X position to the end of the text
x -= getTextWidth(text);
break;
}
case TEXT_ALIGN_CENTER:
{
x -= getTextWidth(text) / 2.0;
// shift the current X position to the end of the text
xCurr = x + getTextWidth(text);
break;
}
}
if (!text.isEmpty())
{
addTextObject(text, x, yCurr);
}
}
/**
* 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 void drawTextBetween(double left, double right, String text, String align)
{
// simple protection from empty style value
if (align == null || align.isEmpty())
{
return;
}
// some special cases that can happen in application
if (left == right)
{
// when both positions are 0.0 we just do not print anything
if (left > 0.0)
{
drawTextAtX(left, text, align);
}
return;
}
// currently used text alignment
int xTextAlign = textAlignHorizCurr;
// check if we need to override text alignment
align = align.toLowerCase();
if (align.equals("left"))
{
xTextAlign = TEXT_ALIGN_LEFT;
}
else if (align.equals("right"))
{
xTextAlign = TEXT_ALIGN_RIGHT;
}
// need to truncate part of the text that does not fit the region
double boundWidth = right - left;
// omit spaces from trailing part of the string
int textLength = StringHelper.safeTrimTrailing(text).length();
for (int i = textLength - 1; i >= 0; i--)
{
String textMatch = text.substring(0, i + 1);
if (getTextWidth(textMatch) <= boundWidth)
{
int textMatchLength = textMatch.length();
if (textMatchLength < textLength)
{
int dotLength = Math.min(textMatchLength, 3);
text = textMatch.substring(0, textMatchLength - dotLength) + "...";
}
else
{
text = textMatch;
}
break;
}
}
// consider current alignment and adjust X position
switch (xTextAlign)
{
case TEXT_ALIGN_LEFT:
{
// adjust right point to the minimum of current right and text width plus text start
right = Math.min(left + getTextWidth(text), right);
break;
}
case TEXT_ALIGN_RIGHT:
{
left = right - getTextWidth(text);
break;
}
case TEXT_ALIGN_CENTER:
{
left -= getTextWidth(text) / 2.0;
break;
}
}
// if even single letter doed not fit, no need to create text object because nothing to draw
if (!text.isEmpty())
{
addTextObject(text, left, yCurr);
}
// shift the current X position to the end of the text
xCurr = right;
}
/**
* 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 void setLineAttributes(String style, double thickness,
int redColor, int greenColor, int blueColor)
{
// line style and thick
setLineStyle(style, thickness);
// and color
lineColor = (redColor & 0x000000FF) | (greenColor & 0x000000FF) << 8 |
(blueColor & 0x000000FF) << 16;
}
/**
* 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 void setLineColor(int rgbColor)
{
lineColor = rgbColor;
}
/**
* 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 void setLineStyle(String style, double thickness)
{
// simple protection from empty style value
if (style != null && !style.isEmpty())
{
lineStyle = getLineType(style.toLowerCase());
}
if (thickness == 0.0)
{
lineWidth = 1;
}
else
{
lineWidth = MetafileHelper.mfUnits2pix(thickness) + 1;
}
}
/**
* 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 void setFillColor(int rgbColor)
{
fillColor = rgbColor;
}
/**
* 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 void drawLine(double left, double top, double right, double bottom)
{
// coordinates check
if (top > bottom)
{
// need to have bottom >= top condition
double swapY = top;
top = bottom;
bottom = swapY;
}
if (left > right)
{
// need to have bottom >= top condition
double swapX = left;
left = right;
right = swapX;
}
// make some adjustments for linex with left edge X coord
if (left == 0.0)
{
left = EDGE_LINE_X_SHIFT;
}
if (right == 0.0)
{
right = EDGE_LINE_X_SHIFT;
}
// and some Y adjust to the all vertical lines
if (left == right)
{
top += VERT_LINE_Y_SHIFT;
bottom += VERT_LINE_Y_SHIFT;
}
XprObjLine xprLine = new XprObjLine(lineColor, MetafileHelper.y2Row(top + OBJ_Y_SHIFT),
MetafileHelper.x2Col(left),
MetafileHelper.y2Row(bottom + OBJ_Y_SHIFT),
MetafileHelper.x2Col(right), lineWidth, lineStyle);
xprLine.setMargins(leftMargin, topMargin);
objects.add(xprLine);
}
/**
* 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 void drawRect(double left, double top, double right, double bottom)
{
// coordinates check
if (top > bottom)
{
// need to have bottom >= top condition
double swapY = top;
top = bottom;
bottom = swapY;
}
if (left > right)
{
// need to have bottom >= top condition
double swapX = left;
left = right;
right = swapX;
}
XprObjRectangle xprRect = new XprObjRectangle(lineColor, MetafileHelper.y2Row(top + OBJ_Y_SHIFT),
MetafileHelper.x2Col(left),
MetafileHelper.y2Row(bottom + OBJ_Y_SHIFT),
MetafileHelper.x2Col(right),
lineWidth, false, lineStyle);
xprRect.setMargins(leftMargin, topMargin);
objects.add(xprRect);
}
/**
* 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 void drawFilledRect(double left, double top, double right, double bottom)
{
// coordinates check
if (top > bottom)
{
// need to have bottom >= top condition
double swapY = top;
top = bottom;
bottom = swapY;
}
if (left > right)
{
// need to have bottom >= top condition
double swapX = left;
left = right;
right = swapX;
}
// small boxes need additional adjust
if (bottom - top < 2.0 * (getFontHeight() + extraRowDistance))
{
bottom += 0.03;
}
// filled rectangle needs special attention because the border should not be painted
XprObjFilledRectangle xprFillRect =
new XprObjFilledRectangle(fillColor, fillColor, MetafileHelper.y2Row(top + OBJ_Y_SHIFT + 0.04),
MetafileHelper.x2Col(left + 0.01),
MetafileHelper.y2Row(bottom + OBJ_Y_SHIFT),
MetafileHelper.x2Col(right), lineWidth, false, lineStyle);
xprFillRect.setMargins(leftMargin, topMargin);
objects.add(xprFillRect);
}
/**
* 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 void setImageAlign(String horizAlign, String vertAlign)
{
// horizontal alignment type
if (horizAlign != null && !horizAlign.isEmpty())
{
horizAlign = horizAlign.toLowerCase();
if (horizAlign.equals("left"))
{
imageAlignHorizCurr = IMAGE_ALIGN_LEFT;
}
else if (horizAlign.equals("center"))
{
imageAlignHorizCurr = IMAGE_ALIGN_CENTER;
}
else if (horizAlign.equals("right"))
{
imageAlignHorizCurr = IMAGE_ALIGN_RIGHT;
}
}
// vetical alignment type
if (vertAlign != null && !vertAlign.isEmpty())
{
vertAlign = vertAlign.toLowerCase();
if (vertAlign.equals("top"))
{
imageAlignVertCurr = IMAGE_ALIGN_TOP;
}
else if (vertAlign.equals("center"))
{
imageAlignVertCurr = IMAGE_ALIGN_CENTER;
}
else if (vertAlign.equals("bottom"))
{
imageAlignVertCurr = IMAGE_ALIGN_BOTTOM;
}
}
}
/**
* 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 void drawImage(String option, double x, double y, double width, double height,
String filename, decimal imgWidth, decimal imgHeight)
{
// TODO: verify this
if (FwdJasperExtensionRegistry.getRepositoryService().getInputStream(filename) != null ||
XprHelper.getInputStream(filename) != null || XprHelper.onErrorImageSilentMode())
{
// consider current alignment and adjust X and Y positions
switch (imageAlignHorizCurr)
{
case IMAGE_ALIGN_RIGHT:
{
x -= width;
break;
}
case IMAGE_ALIGN_CENTER:
{
x -= width / 2.0;
break;
}
}
switch (imageAlignVertCurr)
{
case IMAGE_ALIGN_BOTTOM:
{
y -= height;
break;
}
case IMAGE_ALIGN_CENTER:
{
x -= height / 2.0;
break;
}
}
// set up image object
x += 0.15;
y += 0.15 + OBJ_Y_SHIFT;
XprObjImage xprImage = new XprObjImage(MetafileHelper.y2Row(y), MetafileHelper.x2Col(x),
MetafileHelper.y2Row(y + height),
MetafileHelper.x2Col(x + width), filename);
xprImage.setMargins(leftMargin, topMargin);
objects.add(xprImage);
}
if (imgWidth != null)
{
imgWidth.assign(new decimal(width));
}
if (imgHeight != null)
{
imgHeight.assign(new decimal(height));
}
}
/**
* 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 maxWidth
* The image maximum right border not to exceed.
* @param filename
* The name of the file to use as image.
*/
public void drawImagePart(double x, double y, int widthPix, int heightPix,
double maxWidth, String filename)
{
double width = MetafileHelper.pix2MfUnits(widthPix);
double height = MetafileHelper.pix2MfUnits(heightPix);
// if width > maxWidth adjust height accordingly
if (width > maxWidth)
{
height *= maxWidth / width;
width = maxWidth;
}
// cal generic primitive keeping aspect
drawImage("origratio=yes", x, y, width, height, filename, null, null);
}
/**
* Sets new value for current page orientation.
*
* @param mode
* The new String value for page orientation. The valid alignment values:
* "standard", "portrait", "landscape".
*/
public void setPageOrientation(String mode)
{
// simple protection from empty mode value
if (mode == null || mode.isEmpty())
{
return;
}
// normalize oriantation name
mode = mode.toLowerCase();
// update page orientation
if (mode.equals("standard") || mode.equals("portrait"))
{
pageOrientation = XprPageOptions.PORTRAIT;
}
else if (mode.equals("landscape"))
{
pageOrientation = XprPageOptions.LANDSCAPE;
}
// Update page size for new orientation
updatePageOptions();
}
/**
* Gets the current page width.
*
* @param pageWidth
* The current page width value in device independent units.
*/
public void getPageWidth(decimal pageWidth)
{
pageWidth.assign(new decimal(diPageWidth));
}
/**
* Gets the current page height.
*
* @param pageHeight
* The current page height value in device independent units.
*/
public void getPageHeight(decimal pageHeight)
{
pageHeight.assign(new decimal(diPageHeight - footerHeight));
}
/**
* Sets new value for current/maximum page number.
*
* @param mode
* The new String value for page mode. The valid alignment values: "append", "new".
* @param newMaxPageNum
* The integer max page number value.
*/
public void resetPage(String mode, int newMaxPageNum)
{
// simple protection from empty mode value
if (mode == null || mode.isEmpty())
{
return;
}
// check for the mode to continue
if (mode.equals("append"))
{
if (currPageNum == maxPageNum && maxPageNum < newMaxPageNum)
{
maxPageNum = newMaxPageNum;
}
}
else if (mode.equals("new"))
{
if (maxPageNum < newMaxPageNum)
{
startNewPage();
}
}
}
/**
* Gets the current page number.
*
* @param pageNum
* The current integer page number value.
*/
public void getPageNumber(integer pageNum)
{
pageNum.assign(new integer(currPageNum));
}
/**
* Sets new value for current page number.
*
* @param newPageNum
* The integer page number value.
*/
public void setPageNumber(int newPageNum)
{
currPageNum = 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 void setPageNumberPosition(double x, double y)
{
xPageNum = x;
yPageNum = y + 0.15;
}
/**
* Sets new value for current page number text.
*
* @param text
* The new text for page number.
* @param pgNumberNdx
* The 1-based index of new text for page number.
*/
public void setPageNumberText(String text, int pgNumberNdx)
{
// the X and Y positions will be set later
XprObjText xprText = new XprObjText(currFontName, fontSize, isBold, isItalic,
textColor, 0.0, 0.0, text);
xprText.setMargins(leftMargin, topMargin);
pageNumberTextObj.add(xprText);
}
/**
* Sets to show page footer or not.
*
* @param show
* The <code>TRUE</code> value means show footer, <code>FALSE</code> means no footer.
*/
public void showPageFooter(boolean show)
{
showFooter = show;
}
/**
* 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 void initialize(double footerTextHeight, String tmpDir, integer ok)
{
// reserve several lines for page footer
footerHeight = 1.5 * footerTextHeight;
pdfDir = tmpDir;
ok.assign(new integer(1));
}
/**
* Stops all operation for the current metafile and commit remaining objects to the current page.
*/
public void stopRecording()
{
// already stopped current recording
if (recordingStopped)
{
return;
}
// commit remaining objects
if (objects.size() > 0)
{
pages.add(objects);
}
// now add the optional page number texts
int pageNumberObjSize = pageNumberTextObj.size();
if (pageNumberObjSize > 0)
{
// prepare total pages string value to put into every page
int pagesTotalInt = pages.size();
String pagesTotalStr = String.valueOf(pagesTotalInt);
// modify total pages number object
XprObjText xprTextTotal = pageNumberTextObj.get(2);
if (xprTextTotal != null)
{
xprTextTotal.appendText(pagesTotalStr);
}
// need to walk through all pages
for (int i = 0; i < pagesTotalInt; i++)
{
List<XprObjBase> pageCurr = getObjectsInPage(i);
double xPageNumCurrent = xPageNum;
// now enumerate all page number texts in reverse order and finalize object preparation
for (int j = pageNumberObjSize - 2; j >= 0; j--)
{
XprObjText xprOldText = pageNumberTextObj.get(j);
float customFontSize = xprOldText.getFontSize();
XprObjText xprNewText = null;
String newFooterText = null;
switch (j)
{
case 1:
{
// need to crate new text object for every page in the document,
// because this part of the text is different for each page
newFooterText = xprOldText.getValue() + String.valueOf(i + 1) +
(xprTextTotal != null ? xprTextTotal.getValue() : "");
break;
}
case 0:
{
newFooterText = xprOldText.getValue();
break;
}
}
if (newFooterText != null)
{
xPageNumCurrent -= getTextWidth(newFooterText, customFontSize,
xprOldText.isBold(), xprOldText.isItalic());
xprNewText = new XprObjText(xprOldText.getFontName(), customFontSize,
xprOldText.isBold(), xprOldText.isItalic(),
xprOldText.getColor(), MetafileHelper.y2Row(yPageNum),
MetafileHelper.x2Col(xPageNumCurrent), newFooterText);
pageCurr.add(xprNewText);
}
}
}
}
// clean up page number list
pageNumberTextObj.clear();
// all done
recordingStopped = true;
}
/**
* Gets optionally set temporary directory to store generated PDF temporary files.
*
* @return The currently used temporary directory.
*/
public String getTmpDirectory()
{
return pdfDir;
}
/**
* Converts line style string into JasperReports compatible line type.
*
* @param lineType
* The line type lower cased string.
*
* @return The JasperReport compatible line type.
*/
private LineStyleEnum getLineType(String lineType)
{
// default line type is solid
LineStyleEnum lsRet = LineStyleEnum.SOLID;
if (lineType.equals("dash"))
{
lsRet = LineStyleEnum.DASHED;
}
else if (lineType.equals("dot"))
{
lsRet = LineStyleEnum.DOTTED;
}
return lsRet;
}
/**
* Set up options for new current document page.
*/
private void setPageOptions()
{
pageOptions.add(new XprPageOptions(XprHelper.getPdfPageWidth(), XprHelper.getPdfPageHeight(),
pageOrientation));
diPageWidth = MetafileHelper.pix2MfUnits(pageOptions.get(currPageNum - 1).getWidthPix());
diPageHeight = MetafileHelper.pix2MfUnits(pageOptions.get(currPageNum - 1).getHeightPix());
}
/**
* Update options for new current document page.
*/
private void updatePageOptions()
{
pageOptions.get(currPageNum - 1).setPageOrientation(pageOrientation);
diPageWidth = MetafileHelper.pix2MfUnits(pageOptions.get(currPageNum - 1).getWidthPix());
diPageHeight = MetafileHelper.pix2MfUnits(pageOptions.get(currPageNum - 1).getHeightPix());
}
/**
* Internal method to construct and add new text object.
*
* @param textToAdd
* The line of the text to be added..
* @param xText
* The X text position.
* @param yText
* The Y text position.
*/
private void addTextObject(String textToAdd, double xText, double yText)
{
// consider current alignment and adjust Y position
switch (textAlignVertCurr)
{
case TEXT_ALIGN_BASELINE:
{
yText -= 0.125 + fontSizeMetric / 2.0;
// shift the current X position to the end of the text
break;
}
case TEXT_ALIGN_BOTTOM:
{
yText -= fontSizeMetric;
// shift the current X position to the end of the text
break;
}
}
XprObjText xprText = new XprObjText(currFontName, fontSize, isBold, isItalic,
textColor, MetafileHelper.y2Row(yText + OBJ_Y_SHIFT),
MetafileHelper.x2Col(xText), textToAdd);
xprText.setMargins(leftMargin, topMargin);
objects.add(xprText);
// simulate underlined text with additional line when necessary
if (isUnderline)
{
yText += getFontHeight();
// current line width may be other than 1 we need to keep this value untouched
int savedLineWidth = lineWidth;
lineWidth = 2;
drawLine(xText - 0.01, yText, xText + getTextWidth(textToAdd) + 0.085, yText);
lineWidth = savedLineWidth;
}
}
//-------------------- Implementation of the PdfProvoder interface ---------------------------
/**
* Getting internal object list from XPR object for particular page.
*
* @param pageNum
* 0 based page number for objects to get.
*
* @return Z-ordered list of the objects recognized inside currently processed XPR file
* for given page number.
*/
@Override
public List<XprObjBase> getObjectsInPage(int pageNum)
{
return pages.get(pageNum);
}
/**
* Getting total number of pages in document.
*
* @return The number of pages with objects.
*/
@Override
public int getTotalPageNumbers()
{
return pages.size();
}
/**
* Getting the page width for given page numbers.
*
* @param pageNum
* 0 based page number for option to get.
*
* @return The page width in pixels or -1 for invalid page num.
*/
@Override
public int getPageWidth(int pageNum)
{
return pageNum < 0 || pageNum > pageOptions.size() - 1
? -1 : pageOptions.get(pageNum).getWidthPix();
}
/**
* Getting the page height for given page numbers.
*
* @param pageNum
* 0 based page number for option to get.
*
* @return The page width in pixels.
*/
@Override
public int getPageHeight(int pageNum)
{
return pageNum < 0 || pageNum > pageOptions.size() - 1
? -1 : pageOptions.get(pageNum).getHeightPix();
}
//-------------------- Implementation of the PdfProvoder interface ---------------------------
}