/******************************************************************************
 * Product: Adempiere ERP & CRM Smart Business Solution                       *
 * Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved.                *
 * This program is free software; you can redistribute it and/or modify it    *
 * under the terms version 2 of the GNU General Public License as published   *
 * by the Free Software Foundation. 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 General Public License for more details.                       *
 * You should have received a copy of the GNU General Public License along    *
 * with this program; if not, write to the Free Software Foundation, Inc.,    *
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.                     *
 * For the text or an alternative of this public license, you may reach us    *
 * ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA        *
 * or via info@compiere.org or http://www.compiere.org/license.html           *
 *****************************************************************************/
package org.compiere.print.layout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.io.Serializable;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.regex.Pattern;
import org.compiere.model.MQuery;
import org.compiere.print.MPrintFormatItem;
import org.compiere.print.MPrintTableFormat;
import org.compiere.print.PrintData;
import org.compiere.print.util.SerializableMatrix;
import org.compiere.print.util.SerializableMatrixImpl;
import org.compiere.report.MReportLine;
import org.compiere.util.Evaluator;
import org.compiere.util.KeyNamePair;
import org.compiere.util.NamePair;
import org.compiere.util.Util;
import org.compiere.util.ValueNamePair;
/**
 *	Table Print Element.
 *  Maintains a logical cross page table, which is "broken up" when printing.
 *  
* The table is 3 pages wide, 2 pages high * +-----+-----+-----+ * | 1.1 | 1.2 | 1.3 | * +-----+-----+-----+ * | 2.1 | 2.2 | 2.3 | * +-----+-----+-----+ * Printed * +-----+-----+-----+ * | 1 | 2 | 3 | * +-----+-----+-----+ * | 4 | 5 | 6 | * +-----+-----+-----+ ** @author Jorg Janke * @version $Id: TableElement.java,v 1.2 2006/07/30 00:53:02 jjanke Exp $ * * @author Teo Sarca, SC ARHIPAC SERVICE SRL *
     *  The rowCol.. maps are organized as follows - Point (row,col)
     *  	row    - data if 0..m - if -1 for the entire column
     *  	column - data if 0..n - if -1 for the entire row
     *  i.e. Point (-1, -1) is the default for the table
     *  
     *  @param columnHeader array with column headers (Key=ColumnName)
     *  @param columnMaxWidth array with column max width - 0=no restrictions - negative=suppress if null
     *  @param columnMaxHeight array with row max height for a column - 0=no restrictions; -1=one row only
     *  @param columnJustification field justification for column
     *
     *  @param fixedWidth array with column fixed width
     *  @param functionRows list of function rows
     *  @param multiLineHeader if true, the header is not truncated at maxWidth
     *
     *  @param data 2D array with data to be printed [row][col]
     *  @param pk array of primary keys
     *  @param pkColumnName primary key name
     *
     *  @param pageNoStart page number of starting page
     *  @param firstPage bounds on first page
     *  @param nextPages bounds on following pages
     *  @param repeatedColumns repeat first x columns on - X Axis follow pages
     *  @param additionalLines map of old column to below printed column
     *
     *  @param rowColFont HashMap with Point as key with Font overwrite
     *  @param rowColColor HashMap with Point as key with foreground Color overwrite
     *  @param rowColBackground HashMap with Point as key with background Color overwrite
     *  @param tFormat table format
     *  @param pageBreak Arraylist of rows with page break
     *
     *  @param colSuppressRepeats
     *  @param rowColReportLine
     *  @param finReportSumRows
     */
    public TableElement (ValueNamePair[] columnHeader,
        int[] columnMaxWidth, int[] columnMaxHeight, String[] columnJustification,
        boolean[] fixedWidth, ArrayList
     *  Examples:
     *    From general to specific:
     *      (-1,-1) => for entire table
     *      (-1, c) => for entire column c
     *      (r, -1) => for entire row r (overwrites column)
     *      (r, c)  => for specific cell (highest priority)
     *    Header is row -2
     *      (-2,-1) => for all header columns
     *      (-2, c) => for header column c
     *  
     * 	@param row row
     * 	@param col column
     * 	@return Font for row/col
     */
    private Font getFont (int row, int col)
    {
        //	First specific position
        Font font = (Font)m_rowColFont.get(new Point(row, col));
        if (font != null)
            return font;
        //	Row Next
        font = (Font)m_rowColFont.get(new Point (row, ALL));
        if (font != null)
            return font;
        //	Column then
        font = (Font)m_rowColFont.get(new Point (ALL, col));
        if (font != null)
            return font;
        //	default
        return m_baseFont;
    }	//	getFont
    /**
     * 	Get Foreground Color.
     * 	@param row row
     * 	@param col column
     * 	@return Foreground Color for row/col
     */
    private Color getColor (int row, int col)
    {
        //	First specific position
        Color color = (Color)m_rowColColor.get(new Point(row, col));
        if (color != null)
            return color;
        //	Row Next
        color = (Color)m_rowColColor.get(new Point (row, ALL));
        if (color != null)
            return color;
        //	Column then
        color = (Color)m_rowColColor.get(new Point (ALL, col));
        if (color != null)
            return color;
        //	default
        return m_baseColor;
    }	//	getFont
    /**
     * 	Get Background Color.
     * 	@param row row
     * 	@param col column
     * 	@return Background Color for row/col
     */
    private Color getBackground (int row, int col)
    {
        //	First specific position
        Color color = (Color)m_rowColBackground.get(new Point(row, col));
        if (color != null)
            return color;
        //	Row Next
        color = (Color)m_rowColBackground.get(new Point (row, ALL));
        if (color != null)
            return color;
        //	Column then
        color = (Color)m_rowColBackground.get(new Point (ALL, col));
        if (color != null)
            return color;
        //	default
        return m_baseBackground;
    }	//	getFont
    /**
     * 	Get Calculated Height on page
     *  @param pageNo page number
     * 	@return Height
     */
    public float getHeight (int pageNo)
    {
        int pageIndex = getPageIndex(pageNo);
        int pageYindex = getPageYIndex(pageIndex);
        if (log.isLoggable(Level.FINE)) log.fine("Page=" + pageNo + " - PageIndex=" + pageIndex
            + ", PageYindex=" +  pageYindex);
        float pageHeight = ((Float)m_pageHeight.get(pageYindex)).floatValue();
        float pageHeightPrevious = 0f;
        if (pageYindex > 0)
            pageHeightPrevious = ((Float)m_pageHeight.get(pageYindex-1)).floatValue();
        float retValue = pageHeight - pageHeightPrevious;
        if (log.isLoggable(Level.FINE)) log.fine("Page=" + pageNo + " - PageIndex=" + pageIndex + ", PageYindex=" +  pageYindex + ", Height=" + String.valueOf(retValue));
        return retValue;
    }	//	getHeight
    /**
     * 	Get Calculated Width on page
     *  @param pageNo page number
     * 	@return Width
     */
    public float getWidth (int pageNo)
    {
        int pageIndex = getPageIndex(pageNo);
        if (pageIndex == 0)
            return m_firstPage.width;
        return m_nextPages.width;
    }	//	getHeight
    /**
     * 	Get number of "real" pages.
     * 	@return page count
     */
    public int getPageCount()
    {
        return m_firstRowOnPage.size() * m_firstColumnOnPage.size();
    }	//	getPageCount
    /**
     * 	Get zero based Page Index within Layout
     * 	@param pageNo real page no
     * 	@return page index
     */
    protected int getPageIndex (int pageNo)
    {
        int index = pageNo - m_pageNoStart;
        if (index < 0)
            log.log(Level.SEVERE, "index=" + index, new Exception());
        return index;
    }	//	getPageIndex
    /**
     * 	Get X - Page Index.
     *  The table is 3 pages wide, 2 pages high - index
     *      +-----+-----+-----+
     *      | 0.0 | 0.1 | 0.2 |
     *      +-----+-----+-----+
     *      | 1.0 | 1.1 | 1.2 |
     *      +-----+-----+-----+
     *  Page Index
     *      +-----+-----+-----+
     *      |  0  |  1  |  2  |
     *      +-----+-----+-----+
     *      |  3  |  4  |  5  |
     *      +-----+-----+-----+
     *  
     * 	@param pageIndex zero based page index
     * 	@return page index on X axis
     */
    protected int getPageXIndex (int pageIndex)
    {
        int noXpages = m_firstColumnOnPage.size();
        int x = pageIndex % noXpages;
        return x;
    }	//	getPageXIndex
    /**
     * 	Get X - Page Count
     * 	@return X page count
     */
    protected int getPageXCount ()
    {
        return m_firstColumnOnPage.size();
    }	//	getPageXCount
    /**
     * 	Get Y | Page Index.
     *  The table is 3 pages wide, 2 pages high - index
     *      +-----+-----+-----+
     *      | 0.0 | 0.1 | 0.2 |
     *      +-----+-----+-----+
     *      | 1.0 | 1.1 | 1.2 |
     *      +-----+-----+-----+
     *  Page Index
     *      +-----+-----+-----+
     *      |  0  |  1  |  2  |
     *      +-----+-----+-----+
     *      |  3  |  4  |  5  |
     *      +-----+-----+-----+
     *  
     * 	@param pageIndex zero based page index
     * 	@return page index on Y axis
     */
    protected int getPageYIndex (int pageIndex)
    {
        int noXpages = m_firstColumnOnPage.size();
        int y = (pageIndex - (pageIndex % noXpages)) / noXpages;
        return y;
    }	//	getPageYIndex
    /**
     * 	Get Y | Page Count
     * 	@return Y page count
     */
    protected int getPageYCount ()
    {
        return m_firstRowOnPage.size();
    }	//	getPageYCount
    /**
     * 	Get Drill Down Query
     * 	@param relativePoint point to find print element
     *  @param pageNo page number
     * 	@return drill down query of print element or null
     */
    @Override
    public MQuery getDrillDown (Point relativePoint, int pageNo)
    {
        if (m_rowColDrillDown.size() == 0)
            return null;
        if (!getBounds(pageNo).contains(relativePoint))
            return null;
        int row = getRow (relativePoint.y, pageNo);
        if (row == -1)
            return null;
        int col = getCol (relativePoint.x, pageNo);
        if (col == -1)
            return null;
        if (log.isLoggable(Level.FINE)) log.fine("Row=" + row + ", Col=" + col + ", PageNo=" + pageNo);
        //
        NamePair pp = (NamePair)m_rowColDrillDown.get(new Point(row,col));
        if (pp == null)
            return null;
        String columnName = MQuery.getZoomColumnName(m_columnHeader[col].getID());
        String tableName = MQuery.getZoomTableName(columnName);
        Object code = pp.getID();
        if (pp instanceof KeyNamePair)
            code = Integer.valueOf(((KeyNamePair)pp).getKey());
        //
        MQuery query = new MQuery(tableName);
        query.addRestriction(columnName, MQuery.EQUAL, code, null, pp.toString());
        return query;
    }	//	getDrillDown
    /**
     * 	Get Drill Across Query
     * 	@param relativePoint point to find print element
     *  @param pageNo page number
     * 	@return drill across query of print element or null
     */
    @Override
    public MQuery getDrillAcross (Point relativePoint, int pageNo)
    {
        if (!getBounds(pageNo).contains(relativePoint))
            return null;
        int row = getRow (relativePoint.y, pageNo);
        if (row == -1)
            return null;
        if (log.isLoggable(Level.FINE)) log.fine("Row=" + row  + ", PageNo=" + pageNo);
        //
        if (m_pk[row] == null)	//	FunctionRows
            return null;
        return MQuery.getEqualQuery(m_pkColumnName, m_pk[row].getKey());
    }	//	getDrillAcross
    /**
     * 	Get relative Bounds of Element.
     *  (entire page, not just used portion)
     *  @param pageNo pageNo
     * 	@return bounds
     */
    public Rectangle getBounds(int pageNo)
    {
        int pageIndex = getPageIndex(pageNo);
        int pageYindex = getPageYIndex(pageIndex);
        if (pageYindex == 0)
            return m_firstPage;
        else
            return m_nextPages;
    }	//	getBounds
    /**
     * 	Get Row for yPos
     * 	@param yPos y position (page relative)
     *  @param pageNo page number
     * 	@return row index or -1
     */
    private int getRow (int yPos, int pageNo)
    {
        int pageIndex = getPageIndex(pageNo);
        int pageYindex = getPageYIndex(pageIndex);
        //
        int curY = (pageYindex == 0 ? m_firstPage.y : m_nextPages.y) + m_headerHeight;
        if (yPos < curY)
            return -1;		//	above
        //
        int firstRow = ((Integer)m_firstRowOnPage.get(pageYindex)).intValue();
        int nextPageRow = m_data.getRowCount();				//	no of rows
        if (pageYindex+1 < m_firstRowOnPage.size())
            nextPageRow = ((Integer)m_firstRowOnPage.get(pageYindex+1)).intValue();
        //
        for (int row = firstRow; row < nextPageRow; row++)
        {
            int rowHeight = ((Float)m_rowHeights.get(row)).intValue();	//	includes 2*Gaps+Line
            if (yPos >= curY && yPos < (curY + rowHeight))
                return row;
            curY += rowHeight;
        }
        //	below
        return -1;
    }	//	getRow
    /**
     * 	Get Column for xPos
     * 	@param xPos x position (page relative)
     *  @param pageNo page number
     * 	@return column index or -1
     */
    private int getCol (int xPos, int pageNo)
    {
        int pageIndex = getPageIndex(pageNo);
        int pageXindex = getPageXIndex(pageIndex);
        //
        int curX = pageXindex == 0 ? m_firstPage.x : m_nextPages.x;
        if (xPos < curX)
            return -1;		//	too left
        int firstColumn = ((Integer)m_firstColumnOnPage.get(pageXindex)).intValue();
        int nextPageColumn = m_columnHeader.length;		// no of cols
        if (pageXindex+1 < m_firstColumnOnPage.size())
            nextPageColumn = ((Integer)m_firstColumnOnPage.get(pageXindex+1)).intValue();
        //	fixed volumns
        int regularColumnStart = firstColumn;
        for (int col = 0; col < m_repeatedColumns; col++)
        {
            int colWidth = ((Float)m_columnWidths.get(col)).intValue();		//	includes 2*Gaps+Line
            if (xPos >= curX && xPos < (curX + colWidth))
                return col;
            curX += colWidth;
            if (regularColumnStart == col)
                regularColumnStart++;
        }
        //	regular columns
        for (int col = regularColumnStart; col < nextPageColumn; col++)
        {
            int colWidth = ((Float)m_columnWidths.get(col)).intValue();		//	includes 2*Gaps+Line
            if (xPos >= curX && xPos < (curX + colWidth))
                return col;
            curX += colWidth;
        }	//	for all columns
        //	too right
        return -1;
    }	//	getCol
    /**
     * 	Paint/Print.
     *
     * 	@param g2D Graphics
     *  @param pageNo page number for multi page support (0 = header/footer)
     *  @param pageStart top left Location of page
     *  @param ctx context
     *  @param isView true if online view (IDs are links)
     */
    @Override
    public void paint (Graphics2D g2D, int pageNo, Point2D pageStart, Properties ctx, boolean isView)
    {
        int pageIndex = getPageIndex(pageNo);
        int pageXindex = getPageXIndex(pageIndex);
        int pageYindex = getPageYIndex(pageIndex);
        if (DEBUG_PRINT)
            if (log.isLoggable(Level.CONFIG)) log.config("Page=" + pageNo + " [x=" + pageXindex + ", y=" + pageYindex + "]");
        //
        int firstColumn = ((Integer)m_firstColumnOnPage.get(pageXindex)).intValue();
        int nextPageColumn = m_columnHeader.length;		// no of cols
        if (pageXindex+1 < m_firstColumnOnPage.size())
            nextPageColumn = ((Integer)m_firstColumnOnPage.get(pageXindex+1)).intValue();
        //
        if (pageYindex >= m_firstRowOnPage.size())  {
            pageYindex = m_firstRowOnPage.size() - 1;
        }
        if (pageYindex<0)
            return;
        int firstRow = ((Integer)m_firstRowOnPage.get(pageYindex)).intValue();
        int nextPageRow = m_data.getRowCount();				//	no of rows
        if (pageYindex+1 < m_firstRowOnPage.size())
            nextPageRow = ((Integer)m_firstRowOnPage.get(pageYindex+1)).intValue();
        if (DEBUG_PRINT)
            if (log.isLoggable(Level.FINEST)) log.finest("Col=" + firstColumn + "-" + (nextPageColumn-1)
                + ", Row=" + firstRow + "-" + (nextPageRow-1));
        //	Top Left
        int startX = (int)pageStart.getX();
        int startY = (int)pageStart.getY();
        //	Table Start
        startX += pageIndex == 0 ? m_firstPage.x : m_nextPages.x;
        startY += pageIndex == 0 ? m_firstPage.y : m_nextPages.y;
        if (DEBUG_PRINT)
            if (log.isLoggable(Level.FINEST)) log.finest("PageStart=" + pageStart + ", StartTable x=" + startX + ", y=" + startY);
        //	paint first fixed volumns
        boolean firstColumnPrint = true;
        int regularColumnStart = firstColumn;
        for (int col = 0; col < m_repeatedColumns && col < m_columnWidths.size(); col++)
        {
            int colWidth = ((Float)m_columnWidths.get(col)).intValue();		//	includes 2*Gaps+Line
            if (colWidth != 0)
            {
                printColumn (g2D, col, startX, startY, firstColumnPrint, firstRow, nextPageRow, isView);
                startX += colWidth;
                firstColumnPrint = false;
            }
            if (regularColumnStart == col)
                regularColumnStart++;
        }
        //	paint columns
        for (int col = regularColumnStart; col < nextPageColumn; col++)
        {
            int colWidth = ((Float)m_columnWidths.get(col)).intValue();		//	includes 2*Gaps+Line
            if (colWidth != 0)
            {
                printColumn (g2D, col, startX, startY, firstColumnPrint, firstRow, nextPageRow, isView);
                startX += colWidth;
                firstColumnPrint = false;
            }
        }	//	for all columns
    }	//	paint
    /**
     * 	Print non zero width Column
     * 	@param g2D graphics
     * 	@param col column index
     * 	@param origX start X
     * 	@param origY start Y
     *  @param leftVline if true print left vertical line (for first column)
     * 	@param firstRow first row index
     * 	@param nextPageRow row index of next page
     *  @param isView true if online view (IDs are links)
     */
    private void printColumn (Graphics2D g2D, int col,
        final int origX, final int origY, boolean leftVline,
        final int firstRow, final int nextPageRow, boolean isView)
    {
        int curX = origX;
        int curY = origY;	//	start from top
        //
        float colWidth = ((Float)m_columnWidths.get(col)).floatValue();		//	includes 2*Gaps+Line
        float netWidth = colWidth - (2*H_GAP) - m_tFormat.getVLineStroke().floatValue();
        if (leftVline)
            netWidth -= m_tFormat.getVLineStroke().floatValue();
        float rowHeight = m_headerHeight;
        float netHeight = rowHeight - (4*m_tFormat.getLineStroke().floatValue()) + (2*V_GAP);
        if (DEBUG_PRINT)
            if (log.isLoggable(Level.FINER)) log.finer("#" + col + " - x=" + curX + ", y=" + curY
                + ", width=" + colWidth + "/" + netWidth + ", HeaderHeight=" + rowHeight + "/" + netHeight);
        String alignment = m_columnJustification[col];
        //	paint header	***************************************************
        if (leftVline)			//	draw left | line
        {
            g2D.setPaint(m_tFormat.getVLine_Color());
            g2D.setStroke(m_tFormat.getVLine_Stroke());
            if (m_tFormat.isPaintBoundaryLines())				//	 -> | (left)
                g2D.drawLine(origX, (int)(origY+m_tFormat.getLineStroke().floatValue()),
                    origX, (int)(origY+rowHeight-(4*m_tFormat.getLineStroke().floatValue())));
            curX += m_tFormat.getVLineStroke().floatValue();
        }
        //	X - start line
        if (m_tFormat.isPaintHeaderLines())
        {
            g2D.setPaint(m_tFormat.getHeaderLine_Color());
            g2D.setStroke(m_tFormat.getHeader_Stroke());
            g2D.drawLine(origX, origY, 							//	 -> - (top)
                (int)(origX+colWidth-m_tFormat.getVLineStroke().floatValue()), origY);
        }
        curY += (2 * m_tFormat.getLineStroke().floatValue());	//	thick
        //	Background
        Color bg = getBackground(HEADER_ROW, col);
        if (!bg.equals(Color.white))
        {
            g2D.setPaint(bg);
            g2D.fillRect(curX,
                (int)(curY-m_tFormat.getLineStroke().floatValue()),
                (int)(colWidth-m_tFormat.getVLineStroke().floatValue()),
                (int)(rowHeight-(4*m_tFormat.getLineStroke().floatValue())));
        }
        int tempCurY = curY;
        curX += H_GAP;		//	upper left gap
        curY += V_GAP;
        //	Header
        AttributedString aString = null;
        AttributedCharacterIterator iter = null;
        LineBreakMeasurer measurer = null;
        float usedHeight = 0;
        // Calculate column header height - teo_sarca [ 1673429 ]
        String headerString = m_columnHeader[col].toString();
        if (headerString.length() == 0)
            headerString = " ";
        //if (m_columnHeader[col].toString().length() > 0)
        {
            aString = new AttributedString(headerString);
            aString.addAttribute(TextAttribute.FONT, getFont(HEADER_ROW, col));
            aString.addAttribute(TextAttribute.FOREGROUND, getColor(HEADER_ROW, col));
            //
            boolean fastDraw = LayoutEngine.s_FASTDRAW;
            if (fastDraw && !isView && !Util.is8Bit(headerString))
                fastDraw = false;
            iter = aString.getIterator();
            measurer = new LineBreakMeasurer(iter, g2D.getFontRenderContext());
            while (measurer.getPosition() < iter.getEndIndex())		//	print header
            {
                TextLayout layout = measurer.nextLayout(netWidth + 2);
                if (iter.getEndIndex() != measurer.getPosition())
                    fastDraw = false;
                if (alignment.equals(MPrintFormatItem.FIELDALIGNMENTTYPE_Block))
                {
                    layout = layout.getJustifiedLayout(netWidth + 2);
                    fastDraw = false;
                }
                curY += layout.getAscent();
                float penX = curX;
                if (alignment.equals(MPrintFormatItem.FIELDALIGNMENTTYPE_Center))
                    penX += (netWidth-layout.getAdvance())/2;
                else if ((alignment.equals(MPrintFormatItem.FIELDALIGNMENTTYPE_TrailingRight) && layout.isLeftToRight())
                        || (alignment.equals(MPrintFormatItem.FIELDALIGNMENTTYPE_LeadingLeft) && !layout.isLeftToRight()))
                    penX += netWidth-layout.getAdvance();
                //
                if (fastDraw)
                {	//	Bug - set Font/Color explicitly
                    g2D.setFont(getFont(HEADER_ROW, col));
                    g2D.setColor(getColor(HEADER_ROW, col));
                    g2D.drawString(iter, penX, curY);
                }
                else
                    layout.draw(g2D, penX, curY);										//	-> text
                curY += layout.getDescent() + layout.getLeading();
                usedHeight += layout.getAscent() + layout.getDescent();
                if ( !m_multiLineHeader )			//	one line only
                    break;
            }
        }
        curX += netWidth + H_GAP;
        curY = tempCurY + (int)(rowHeight-(4*m_tFormat.getLineStroke().floatValue()));
        //	Y end line
        g2D.setPaint(m_tFormat.getVLine_Color());
        g2D.setStroke(m_tFormat.getVLine_Stroke());
        if (m_tFormat.isPaintVLines())					//	 -> | (right)
            g2D.drawLine(curX, (int)(origY+m_tFormat.getLineStroke().floatValue()),
                curX, (int)(origY+rowHeight-(4*m_tFormat.getLineStroke().floatValue())));
        curX += m_tFormat.getVLineStroke().floatValue();
        //	X end line
        if (m_tFormat.isPaintHeaderLines())
        {
            g2D.setPaint(m_tFormat.getHeaderLine_Color());
            g2D.setStroke(m_tFormat.getHeader_Stroke());
            g2D.drawLine(origX, curY, 					//	 -> - (button)
                (int)(origX+colWidth-m_tFormat.getVLineStroke().floatValue()), curY);
        }
        curY += (2 * m_tFormat.getLineStroke().floatValue());	//	thick
        //	paint Data		***************************************************
        for (int row = firstRow; row < nextPageRow; row++)
        {
            rowHeight = m_rowHeights.get(row);	//	includes 2*Gaps+Line
            netHeight = rowHeight - (2*V_GAP) - m_tFormat.getLineStroke().floatValue();
            int rowYstart = curY;
            curX = origX;
            if (leftVline)			//	draw left | line
            {
                g2D.setPaint(m_tFormat.getVLine_Color());
                g2D.setStroke(m_tFormat.getVLine_Stroke());
                if (m_tFormat.isPaintBoundaryLines())
                    g2D.drawLine(curX, rowYstart, 				//	 -> | (left)
                        curX, (int)(rowYstart+rowHeight-m_tFormat.getLineStroke().floatValue()));
                curX += m_tFormat.getVLineStroke().floatValue();
            }
            //	Background
            bg = getBackground(row, col);
            if (!bg.equals(Color.white))
            {
                g2D.setPaint(bg);
                g2D.fillRect(curX, curY,
                    (int)(colWidth-m_tFormat.getVLineStroke().floatValue()),
                    (int)(rowHeight-m_tFormat.getLineStroke().floatValue()) );
            }
            // Over Line
            MReportLine rLine = getReportLine(row, col);
            if (rLine != null)
            {
                if (rLine.getOverline() > 1)
                {
                    curY -= V_GAP + m_tFormat.getVLineStroke().floatValue();
                    g2D.setPaint(m_tFormat.getHeaderLine_Color());
                    g2D.setStroke(rLine.getOverlineStroke(m_tFormat.getVLineStroke()));
                    g2D.drawLine(curX, (int) (curY + m_tFormat.getVLineStroke().floatValue()),
                                 (int) (curX + colWidth - m_tFormat.getVLineStroke().floatValue()), (int) (curY + m_tFormat.getVLineStroke().floatValue()));
                    curY += V_GAP + m_tFormat.getVLineStroke().floatValue();
                }
                if (rLine.getOverline() > 0)
                {
                    g2D.setPaint(m_tFormat.getHeaderLine_Color());
                    g2D.setStroke(rLine.getOverlineStroke(m_tFormat.getVLineStroke()));
                    g2D.drawLine(curX, curY, (int) (curX + colWidth - m_tFormat.getVLineStroke().floatValue()), curY);
                    curY += m_tFormat.getVLineStroke().floatValue();
                }
            }
            curX += H_GAP;		//	upper left gap
            curY += V_GAP;
            //	actual data
            Object[] printItems = getPrintItems(row,col);
            float penY = curY;
            // suppress repeated values
            boolean suppress = false;
            if (m_colSuppressRepeats[col] && row > 0 && row != firstRow)
            {
                Object[] lastItems = {};
                lastItems = getPrintItems(row-1, col);
                if (Arrays.equals(lastItems,printItems) )
                    suppress = true;
            }
            if (!suppress)
            {
                if (m_tablePrintData != null && m_pageLogics != null && col < m_pageLogics.size())
                {
                    String pageLogic = m_pageLogics.get(col);
                    if (!Util.isEmpty(pageLogic, true))
                    {
                        m_tablePrintData.setRowIndex(row);
                        PrintDataEvaluatee evaluatee = new PrintDataEvaluatee(getCurrentPage(), m_tablePrintData);
                        boolean display = Evaluator.evaluateLogic(evaluatee, pageLogic);
                        if (!display)
                            suppress = true;
                    }
                }
            }
            if ( !suppress )
            {
                for (int index = 0; index < printItems.length; index++)
                {
                    if (printItems[index] == null )
                        ;
                    else if (printItems[index] instanceof ImageElement)
                    {
                        Image imageToDraw = ((ImageElement)printItems[index]).getImage();
                        if (imageToDraw != null) // teo_sarca [ 1674706 ]
                        {
                            // Draw image using the scale factor - teo_sarca, [ 1673548 ] Image is not scaled in a report table cell
                            double scale = ((ImageElement)printItems[index]).getScaleFactor();
                            if (scale != 1.0) {
                                AffineTransform transform = new AffineTransform();
                                transform.translate(curX, penY);
                                transform.scale(scale, scale);
                                g2D.drawImage(imageToDraw, transform, this);
                            }
                            else {
                                g2D.drawImage(imageToDraw, curX, (int)penY, this);
                            }
                        }
                    }
                    else if (printItems[index] instanceof BarcodeElement)
                    {
                        BarcodeElement barcodeElement = (BarcodeElement)printItems[index];
                        barcodeElement.paint(g2D, curX, (int)penY);
                    }
                    else if (printItems[index] instanceof Boolean)
                    {
                        int penX = curX + (int)((netWidth-LayoutEngine.IMAGE_SIZE.width)/2);	//	center
                        if (((Boolean)printItems[index]).booleanValue())
                            g2D.drawImage(LayoutEngine.IMAGE_TRUE, penX, (int)penY, this);
                        else
                            g2D.drawImage(LayoutEngine.IMAGE_FALSE, penX, (int)penY, this);
                        penY += LayoutEngine.IMAGE_SIZE.height;
                    }
                    else if (printItems[index] instanceof HTMLRenderer)
                    {
                        HTMLRenderer renderer = (HTMLRenderer)printItems[index];
                        Rectangle allocation = new Rectangle((int)colWidth, (int)netHeight);
                        //	log.finest( "printColumn HTML - " + allocation);
                        g2D.translate(curX, penY);
                        renderer.paint(g2D, allocation);
                        g2D.translate(-curX, -penY);
                        penY += allocation.getHeight();
                    }
                    else
                    {
                        String str = printItems[index].toString();
                        if (DEBUG_PRINT)
                            if (log.isLoggable(Level.FINE)) log.fine("row=" + row + ",col=" + col + " - " + str + " 8Bit=" + Util.is8Bit(str));
                        if (str.length() > 0)
                        {
                            usedHeight = 0;
                            String[] lines = Pattern.compile("\n", Pattern.MULTILINE).split(str);
                            for (int lineNo = 0; lineNo < lines.length; lineNo++)
                            {
                                String thisLine = lines[lineNo];
                                if (thisLine.length() == 0)
                                    thisLine = " ";
                                aString = new AttributedString(thisLine);
                                aString.addAttribute(TextAttribute.FONT, getFont(row, col));
                                if (isView && printItems[index] instanceof NamePair)	//	ID
                                {
                                    aString.addAttribute(TextAttribute.FOREGROUND, LINK_COLOR);
                                    aString.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_LOW_ONE_PIXEL, 0, str.length());
                                }
                                else
                                    aString.addAttribute(TextAttribute.FOREGROUND, getColor(row, col));
                                //
                                iter = aString.getIterator();
                                boolean fastDraw = LayoutEngine.s_FASTDRAW;
                                if (fastDraw && !isView && !Util.is8Bit(thisLine))
                                    fastDraw = false;
                                measurer = new LineBreakMeasurer(iter, g2D.getFontRenderContext());
                                while (measurer.getPosition() < iter.getEndIndex())		//	print element
                                {
                                    TextLayout layout = measurer.nextLayout(netWidth + 2);
                                    if (iter.getEndIndex() != measurer.getPosition())
                                        fastDraw = false;
                                    float lineHeight = layout.getAscent() + layout.getDescent() + layout.getLeading();
                                    if ((m_columnMaxHeight[col] <= 0
                                            || (usedHeight + lineHeight) <= m_columnMaxHeight[col])
                                            && (usedHeight + lineHeight) <= netHeight)
                                    {
                                        if (alignment.equals(MPrintFormatItem.FIELDALIGNMENTTYPE_Block) && measurer.getPosition() < iter.getEndIndex())
                                        {
                                            layout = layout.getJustifiedLayout(netWidth + 2);
                                            fastDraw = false;
                                        }
                                        penY += layout.getAscent();
                                        float penX = curX;
                                        if (alignment.equals(MPrintFormatItem.FIELDALIGNMENTTYPE_Center))
                                            penX += (netWidth-layout.getAdvance())/2;
                                        else if ((alignment.equals(MPrintFormatItem.FIELDALIGNMENTTYPE_TrailingRight) && layout.isLeftToRight())
                                                || (alignment.equals(MPrintFormatItem.FIELDALIGNMENTTYPE_LeadingLeft) && !layout.isLeftToRight()))
                                            penX += netWidth-layout.getAdvance();
                                        //
                                        if (fastDraw)
                                        {	//	Bug - set Font/Color explicitly
                                            g2D.setFont(getFont(row, col));
                                            if (isView && printItems[index] instanceof NamePair)	//	ID
                                            {
                                                g2D.setColor(LINK_COLOR);
                                                //	TextAttribute.UNDERLINE
                                            }
                                            else
                                                g2D.setColor(getColor(row, col));
                                            g2D.drawString(iter, penX, penY);
                                        }
                                        else
                                            layout.draw(g2D, penX, penY);										//	-> text
                                        if (DEBUG_PRINT)
                                            if (log.isLoggable(Level.FINE)) log.fine("row=" + row + ",col=" + col + " - " + str + " - x=" + penX + ",y=" + penY);
                                        penY += layout.getDescent() + layout.getLeading();
                                        usedHeight += lineHeight;
                                        //
                                        if (m_columnMaxHeight[col] == -1)	//	FirstLineOny
                                            break;
                                    }
                                }	//	print element
                            }	//	for all lines
                        }	//	length > 0
                    }	//	non boolean
                }	//	for all print items
            } // not suppressed
            curY += netHeight + V_GAP;
            curX += netWidth + H_GAP;
            //	Y end line
            g2D.setPaint(m_tFormat.getVLine_Color());
            g2D.setStroke(m_tFormat.getVLine_Stroke());
            if (m_tFormat.isPaintVLines())
                g2D.drawLine(curX, rowYstart, 				//	 -> | (right)
                    curX, (int)(rowYstart+rowHeight-m_tFormat.getLineStroke().floatValue()));
            curX += m_tFormat.getVLineStroke().floatValue();
            // Under Line
            if (rLine != null && rLine.getUnderline() > 0)
            {
                if (rLine.getUnderline() > 1)
                {
                    curY -= V_GAP + m_tFormat.getVLineStroke().floatValue();
                    g2D.setPaint(m_tFormat.getHeaderLine_Color());
                    g2D.setStroke(rLine.getUnderlineStroke(m_tFormat.getVLineStroke()));
                    g2D.drawLine(origX, curY, (int) (origX + colWidth - m_tFormat.getVLineStroke().floatValue()), curY);
                    curY += V_GAP + m_tFormat.getVLineStroke().floatValue();
                }
                if (rLine.getUnderline() > 0)
                {
                    g2D.setPaint(m_tFormat.getHeaderLine_Color());
                    g2D.setStroke(rLine.getUnderlineStroke(m_tFormat.getVLineStroke()));
                    g2D.drawLine(origX, curY, (int) (origX + colWidth - m_tFormat.getVLineStroke().floatValue()), curY);
                }
            }
            // Maintain financial report detail and column section Y position
            if ((int) (rowYstart + rowHeight) > curY)
            {
                curY = (int) (rowYstart + rowHeight);
            }
            //  X end line
            if (row == m_data.getRowCount()-1)         //  last Line
            {
                // left some space between underline and last line
                curY += 2 * V_GAP;
                /**
                 * Bug fix - Bottom line was always displayed whether or not header lines was set to be visible
                 * @author ashley
                 */
                if (m_tFormat.isPaintHeaderLines())
                {
                    g2D.setPaint(m_tFormat.getHeaderLine_Color());
                    g2D.setStroke(m_tFormat.getHeader_Stroke());
                    g2D.drawLine(origX, curY,                   //   -> - (last line)
                        (int)(origX+colWidth-m_tFormat.getVLineStroke().floatValue()), curY);
                    curY += (2 * m_tFormat.getLineStroke().floatValue());   //  thick
                }
                else
                {
                    curY += m_tFormat.getLineStroke().floatValue();
                }
            }
            else
            {
                //  next line is a function column -> underline this
                boolean nextIsFunction = m_functionRows.contains(Integer.valueOf(row+1));
                if (nextIsFunction && m_functionRows.contains(Integer.valueOf(row)))
                    nextIsFunction = false;     //  this is a function line too
                MReportLine nextLine = getReportLine(row + 1, col);
                if (nextIsFunction || (m_finReportSumRows.contains(Integer.valueOf(row + 1)) && nextLine != null
                        && nextLine.getOverline() == 0))
                {
                    g2D.setPaint(m_tFormat.getFunctFG_Color());
                    g2D.setStroke(m_tFormat.getHLine_Stroke());
                    g2D.drawLine(origX, curY, 				// -> - (bottom)
                            (int) (origX + colWidth - m_tFormat.getVLineStroke().floatValue()), curY);
                }
                else if (m_tFormat.isPaintHLines())
                {
                    g2D.setPaint(m_tFormat.getHLine_Color());
                    g2D.setStroke(m_tFormat.getHLine_Stroke());
                    g2D.drawLine(origX, curY,               //   -> - (bottom)
                        (int)(origX+colWidth-m_tFormat.getVLineStroke().floatValue()), curY);
                }
                curY += m_tFormat.getLineStroke().floatValue();
            }
        }	// for all rows
    }	//	printColumn
    /**
     * 	Add Additional Lines to row/col
     * 	@param row row
     * 	@param col col
     * 	@param data data
     */
    private void addPrintLines (int row, int col, Serializable data)
    {
        while (m_printRows.getRowCount() <= row)
            m_printRows.addRow(null);
        m_printRows.setRowIndex(row);
        List