net.sf.jasperreports.engine.export.JRGridLayout Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jasperreports Show documentation
Show all versions of jasperreports Show documentation
Free Java Reporting Library
/*
* JasperReports - Free Java Reporting Library.
* Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
* http://www.jaspersoft.com
*
* Unless you have purchased a commercial license agreement from Jaspersoft,
* the following license terms apply:
*
* This program is part of JasperReports.
*
* JasperReports is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JasperReports 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JasperReports. If not, see .
*/
/*
* Contributors:
* Greg Hilton
*/
package net.sf.jasperreports.engine.export;
import java.awt.Color;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import net.sf.jasperreports.engine.JRBoxContainer;
import net.sf.jasperreports.engine.JRLineBox;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintFrame;
import net.sf.jasperreports.engine.JRPrintPage;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.type.ModeEnum;
import net.sf.jasperreports.engine.util.JRBoxUtil;
import net.sf.jasperreports.engine.util.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Utility class used by grid exporters to create a grid for page layout.
*
* @author Lucian Chirita ([email protected])
*/
public class JRGridLayout
{
private static final Log log = LogFactory.getLog(JRGridLayout.class);
private final ExporterNature nature;
private final List elementList;
private final Map cellSizes;
private final Map cellStyles;
private final Map, EmptyGridCell> emptyCells;
private int width;
private int height;
private int offsetX;
private int offsetY;
private CutsInfo xCuts;
private CutsInfo yCuts;
private Grid grid;
private Map boxesCache;
private boolean hasTopMargin = true;
private boolean hasBottomMargin = true;
private boolean hasLeftMargin = true;
private boolean hasRightMargin = true;
private boolean isNested;
/**
* Constructor.
*
* @param elements the elements that should arranged in a grid
* @param width the width available for the grid
* @param height the height available for the grid
* @param offsetX horizontal element position offset
* @param offsetY vertical element position offset
*/
public JRGridLayout(
ExporterNature nature,
List elements,
int width,
int height,
int offsetX,
int offsetY
)
{
this(
nature,
elements,
width,
height,
offsetX,
offsetY,
null //xCuts
);
}
/**
* Constructor.
*
* @param elements the elements that should arranged in a grid
* @param width the width available for the grid
* @param height the height available for the grid
* @param offsetX horizontal element position offset
* @param offsetY vertical element position offset
* @param xCuts An optional list of pre-calculated X cuts.
*/
public JRGridLayout(
ExporterNature nature,
List elements,
int width,
int height,
int offsetX,
int offsetY,
CutsInfo xCuts
)
{
this.nature = nature;
this.elementList = elements;
// TODO lucianc cache these across report pages?
this.cellSizes = new HashMap();
this.cellStyles = new HashMap();
this.emptyCells = new HashMap, EmptyGridCell>();
this.height = height;
this.width = width;
this.offsetX = offsetX;
this.offsetY = offsetY;
this.xCuts = xCuts;
boxesCache = new HashMap();
layoutGrid(null, elements);
}
/**
* Constructor.
*
* @param width the width available for the grid
* @param height the height available for the grid
* @param offsetX horizontal element position offset
* @param offsetY vertical element position offset
*/
protected JRGridLayout(
JRGridLayout parent,
List elements,
int width,
int height,
int offsetX,
int offsetY,
PrintElementIndex parentElementIndex
)
{
this.nature = parent.nature;
this.elementList = parent.elementList;
this.cellSizes = parent.cellSizes;
this.cellStyles = parent.cellStyles;
this.emptyCells = parent.emptyCells;
this.height = height;
this.width = width;
this.offsetX = offsetX;
this.offsetY = offsetY;
//this constructor is called only in nested grids:
this.isNested = true;
boxesCache = new HashMap();
layoutGrid(parentElementIndex, elements);
}
public JRPrintElement getElement(PrintElementIndex parentIndex, int index)
{
// TODO lucianc keep a cache of current element position?
JRPrintElement element;
if (parentIndex == null)
{
element = elementList.get(index);
}
else
{
JRPrintFrame parentFrame = (JRPrintFrame) getElement(parentIndex.getParentIndex(), parentIndex.getIndex());
element = parentFrame.getElements().get(index);
}
return element;
}
/**
* Constructs the element grid.
* @param parentElementIndex
*/
protected void layoutGrid(PrintElementIndex parentElementIndex, List elements)
{
boolean createXCuts = (xCuts == null);
xCuts = createXCuts ? new CutsInfo() : xCuts;
yCuts = nature.isIgnoreLastRow() ? new CutsInfo(0) : new CutsInfo(height);
if(!isNested && nature.isIgnorePageMargins()) //FIXMEXLS left and right margins are not ignored when all pages on a single sheet
{
// TODO lucianc this is an extra virtualization iteration
setMargins(elements);
if(createXCuts)
{
if(hasLeftMargin)
{
xCuts.removeCutOffset(0);
}
}
if(hasTopMargin)
{
yCuts.removeCutOffset(0);
}
if(hasBottomMargin)
{
yCuts.removeCutOffset(height);
}
}
createCuts(elements, offsetX, offsetY, createXCuts);
// add a cut at the width if it's a nested grid, or if the right margin
// is not to be removed and no element goes beyond the width
if (createXCuts && (isNested
|| (!(nature.isIgnorePageMargins() && hasRightMargin)
&& !(xCuts.hasCuts() && xCuts.getLastCutOffset() >= width))))
{
xCuts.addCutOffset(width);
}
xCuts.use();
yCuts.use();
int colCount = Math.max(xCuts.size() - 1, 0);
int rowCount = Math.max(yCuts.size() - 1, 0);
grid = new Grid(rowCount, colCount);
for(int row = 0; row < rowCount; row++)
{
for(int col = 0; col < colCount; col++)
{
GridCellSize size = cellSize(
xCuts.getCutOffset(col + 1) - xCuts.getCutOffset(col),
yCuts.getCutOffset(row + 1) - yCuts.getCutOffset(row),
1,
1
);
grid.set(row, col, emptyCell(size, null));
}
}
setGridElements(parentElementIndex, elements,
offsetX, offsetY,
0, 0, rowCount, colCount);
width = xCuts.getTotalLength();
height = yCuts.getTotalLength();
}
protected GridCellSize cellSize(int width, int height, int colSpan, int rowSpan)
{
GridCellSize key = new GridCellSize(width, height, colSpan, rowSpan);
GridCellSize size = cellSizes.get(key);
if (size == null)
{
size = key;
cellSizes.put(key, size);
if (log.isTraceEnabled())
{
log.trace(this + " added cell size " + size);
}
}
return size;
}
protected void createCuts(List elements, int elementOffsetX, int elementOffsetY, boolean createXCuts)
{
for (Iterator it = elements.iterator(); it.hasNext();)
{
JRPrintElement element = it.next();
if (nature.isToExport(element))
{
if (createXCuts)
{
xCuts.addCutOffset(element.getX() + elementOffsetX);
xCuts.addCutOffset(element.getX() + element.getWidth() + elementOffsetX);
}
yCuts.addCutOffset(element.getY() + elementOffsetY);
yCuts.addCutOffset(element.getY() + element.getHeight() + elementOffsetY);
JRPrintFrame frame = element instanceof JRPrintFrame ? (JRPrintFrame)element : null;
if (frame != null && nature.isDeep(frame))
{
createCuts(
frame.getElements(),
element.getX() + elementOffsetX + frame.getLineBox().getLeftPadding(),
element.getY() + elementOffsetY + frame.getLineBox().getTopPadding(),
createXCuts
);
}
}
}
}
protected void setMargins(List elements)
{
for (Iterator it = elements.iterator(); it.hasNext();)
{
JRPrintElement element = it.next();
if (nature.isToExport(element))
{
if(hasLeftMargin && element.getX() <= 0)
{
hasLeftMargin = false;
}
if(hasRightMargin && element.getX() >= width - element.getWidth())
{
hasRightMargin = false;
}
if(hasTopMargin && element.getY() <= 0)
{
hasTopMargin = false;
}
if(hasBottomMargin && element.getY() >= height - element.getHeight())
{
hasBottomMargin = false;
}
}
}
}
protected void setGridElements(PrintElementIndex parentIndex, List elements,
int elementOffsetX, int elementOffsetY,
int startRow, int startCol, int endRow, int endCol)
{
for (ListIterator it = elements.listIterator(elements.size()); it.hasPrevious();)
{
JRPrintElement element = it.previous();
int elementIndex = it.nextIndex();
if (nature.isToExport(element))
{
int x = element.getX() + elementOffsetX;
int y = element.getY() + elementOffsetY;
int col1 = xCuts.indexOfCutOffset(x);
int row1 = yCuts.indexOfCutOffset(y);
int col2 = xCuts.indexOfCutOffset(x + element.getWidth());
int row2 = yCuts.indexOfCutOffset(y + element.getHeight());
if (!isOverlap(row1, col1, row2, col2))
{
JRPrintFrame frame = element instanceof JRPrintFrame ? (JRPrintFrame)element : null;
if (frame != null && nature.isDeep(frame))
{
PrintElementIndex frameIndex = new PrintElementIndex(parentIndex, elementIndex);
setGridElements(
frameIndex, frame.getElements(),
x + frame.getLineBox().getLeftPadding(),
y + frame.getLineBox().getTopPadding(),
row1, col1, row2, col2
);
setFrameCellsStyle(frame, row1, col1, row2, col2);
}
else
{
setGridElement(element, parentIndex, elementIndex, row1, col1, row2, col2);
}
}
}
}
if (nature.isHorizontallyMergeEmptyCells())
{
horizontallyMergeEmptyCells(startRow, startCol, endRow, endCol);
}
}
protected EmptyGridCell emptyCell(GridCellSize size, GridCellStyle style)
{
Pair key = new Pair(size, style);
EmptyGridCell cell = emptyCells.get(key);
if (cell == null)
{
cell = new EmptyGridCell(size, style);
emptyCells.put(key, cell);
if (log.isDebugEnabled())
{
log.debug(this + " created empty cell for " + size + " and " + style);
}
}
return cell;
}
protected void horizontallyMergeEmptyCells(int startRow, int startCol, int endRow, int endCol)
{
for (int row = startRow; row < endRow; ++row)
{
int startSpan = -1;
int spanWidth = 0;
int col = startCol;
for (; col < endCol; ++col)
{
JRExporterGridCell cell = grid.get(row, col);
if (isEmpty(cell))
{
if (startSpan == -1)
{
startSpan = col;
}
spanWidth += cell.getWidth();
}
else
{
if (startSpan != -1 && col - startSpan > 1)
{
spanEmptyCell(row, startSpan, spanWidth, col - startSpan);
}
startSpan = -1;
spanWidth = 0;
}
}
if (startSpan != -1 && col - startSpan > 1)
{
spanEmptyCell(row, startSpan, spanWidth, col - startSpan);
}
}
}
protected void spanEmptyCell(int row, int col, int spanWidth, int colSpan)
{
EmptyGridCell spanCell = (EmptyGridCell) grid.get(row, col);
GridCellSize newSize = cellSize(spanWidth, spanCell.getHeight(),
colSpan, spanCell.getRowSpan());
grid.set(row, col, emptyCell(newSize, spanCell.getStyle()));
//TODO set OCCUPIED_CELL?
}
protected boolean isEmpty(JRExporterGridCell cell)
{
return cell.getType() == JRExporterGridCell.TYPE_EMPTY_CELL && ((EmptyGridCell) cell).isEmpty();
}
protected boolean isOverlap(int row1, int col1, int row2, int col2)
{
boolean isOverlap = false;
if (nature.isSpanCells())
{
is_overlap_out:
for (int row = row1; row < row2; row++)
{
for (int col = col1; col < col2; col++)
{
if (!isEmpty(grid.get(row, col)))
{
isOverlap = true;
break is_overlap_out;
}
}
}
}
else
{
isOverlap = !isEmpty(grid.get(row1, col1));
}
return isOverlap;
}
protected void setGridElement(JRPrintElement element, PrintElementIndex parentIndex, int elementIndex,
int row1, int col1, int row2, int col2)
{
yCuts.addUsage(row1, Cut.USAGE_NOT_EMPTY);
xCuts.addUsage(col1, Cut.USAGE_NOT_EMPTY);
int rowSpan = nature.isSpanCells() ? row2 - row1 : 1;
int colSpan = nature.isSpanCells() ? col2 - col1 : 1;
JRExporterGridCell gridCell = new ElementGridCell(
this,
parentIndex,
elementIndex,
cellSize(
element.getWidth(),
element.getHeight(),
colSpan,
rowSpan
));
nature.setXProperties(xCuts, element, row1, col1, row2, col2);
nature.setYProperties(yCuts, element, row1, col1, row2, col2);
if (nature.isSpanCells())
{
OccupiedGridCell occupiedGridCell = new OccupiedGridCell(gridCell);
for (int row = row1; row < row2; row++)
{
for (int col = col1; col < col2; col++)
{
grid.set(row, col, occupiedGridCell);
}
yCuts.addUsage(row, Cut.USAGE_SPANNED);
}
for (int col = col1; col < col2; col++)
{
xCuts.addUsage(col, Cut.USAGE_SPANNED);
}
}
if (col2 - col1 != 0 && row2 - row1 != 0)
{
JRLineBox box = (element instanceof JRBoxContainer)?((JRBoxContainer)element).getLineBox():null;
gridCell.setStyle(cellStyle(null, null, box));
if (nature.isBreakBeforeRow(element))
{
yCuts.addUsage(row1, Cut.USAGE_BREAK);
}
if (nature.isBreakAfterRow(element))
{
yCuts.addUsage(row1 + rowSpan, Cut.USAGE_BREAK);
}
grid.set(row1, col1, gridCell);
}
}
protected GridCellStyle cellStyle(Color backcolor, Color forecolor, JRLineBox box)
{
if (backcolor == null && forecolor == null && box == null)
{
return null;
}
GridCellStyle key = new GridCellStyle(backcolor, forecolor, box);
GridCellStyle style = cellStyles.get(key);
if (style == null)
{
style = key;
cellStyles.put(key, style);
if (log.isTraceEnabled())
{
log.trace(this + " added cell style " + style);
}
}
return style;
}
protected void setFrameCellsStyle(JRPrintFrame frame, int row1, int col1, int row2, int col2)
{
Color backcolor = frame.getModeValue() == ModeEnum.OPAQUE ? frame.getBackcolor() : null;
for (int row = row1; row < row2; row++)
{
for (int col = col1; col < col2; col++)
{
JRExporterGridCell cell = grid.get(row, col);
boolean modifiedStyle = false;
Color cellBackcolor = cell.getBackcolor();
if (cellBackcolor == null)
{
if (frame.getModeValue() == ModeEnum.OPAQUE)
{
cellBackcolor = backcolor;
modifiedStyle = true;
}
}
Color cellForecolor = cell.getForecolor();
if (cellForecolor == null)
{
cellForecolor = frame.getForecolor();
modifiedStyle = true;
}
boolean keepLeft = col == col1;
boolean keepRight = col == col2 - cell.getColSpan();
boolean keepTop = row == row1;
boolean keepBottom = row == row2 - cell.getRowSpan();
JRLineBox cellBox = cell.getBox();
if (keepLeft || keepRight || keepTop || keepBottom)
{
BoxKey key = new BoxKey(frame.getLineBox(), cellBox, keepLeft, keepRight, keepTop, keepBottom);
JRLineBox modBox = boxesCache.get(key);
if (modBox == null)
{
modBox = JRBoxUtil.copyBordersNoPadding(frame.getLineBox(), keepLeft, keepRight, keepTop, keepBottom, cellBox);
boxesCache.put(key, modBox);
}
cellBox = modBox;
modifiedStyle = true;
}
if (modifiedStyle)
{
GridCellStyle newStyle = cellStyle(cellBackcolor, cellForecolor, cellBox);
grid.set(row, col, changeStyle(cell, newStyle));
}
}
}
}
protected JRExporterGridCell changeStyle(JRExporterGridCell cell, GridCellStyle newStyle)
{
if (cell.getType() == JRExporterGridCell.TYPE_EMPTY_CELL)
{
// empty cells are shared so they should not be modified
return emptyCell(cell.getSize(), newStyle);
}
// other types of cells can be modified
cell.setStyle(newStyle);
return cell;
}
/**
* Returns the constructed element grid.
*
* @return the constructed element grid
*/
public Grid getGrid()
{
return grid;
}
/**
* Returns the list of cut points on the X axis for the grid.
*
* @return the list of cut points on the X axis for the grid
*/
public CutsInfo getXCuts()
{
return xCuts;
}
/**
* Returns the list of cut points on the Y axis for the grid.
*
* @return the list of cut points on the Y axis for the grid
*/
public CutsInfo getYCuts()
{
return yCuts;
}
/**
* Returns the width available for the grid.
*
* @return the width available for the grid
*/
public int getWidth()
{
return width;
}
public int getColumnWidth(int col)
{
return xCuts.getCutOffset(col + 1) - xCuts.getCutOffset(col);
}
public int getRowHeight(int row)
{
return yCuts.getCutOffset(row + 1) - yCuts.getCutOffset(row);
}
public int getMaxRowHeight(int rowIndex)
{
GridRow row = grid.getRow(rowIndex);
int maxRowHeight = row.get(0).getHeight();
int rowSize = row.size();
for (int col = 0; col < rowSize; col++)
{
JRExporterGridCell cell = row.get(col);
if (cell.getType() != JRExporterGridCell.TYPE_OCCUPIED_CELL)
{
if (maxRowHeight < cell.getHeight())
{
maxRowHeight = cell.getHeight();
}
}
}
return maxRowHeight;
}
public static int getRowHeight(GridRow row)//FIXMEODT are we still using this?
{
JRExporterGridCell firstCell = row.get(0);
if (firstCell.getRowSpan() == 1 && firstCell.getType() != JRExporterGridCell.TYPE_OCCUPIED_CELL) //quick exit
{
return firstCell.getHeight();
}
int rowHeight = 0;
int minSpanIdx = 0;
int colCount = row.size();
int col;
for (col = 0; col < colCount; col++)
{
JRExporterGridCell cell = row.get(col);
if (cell.getType() != JRExporterGridCell.TYPE_OCCUPIED_CELL)
{
if (cell.getRowSpan() == 1)
{
rowHeight = cell.getHeight();
break;
}
if (cell.getRowSpan() < row.get(minSpanIdx).getRowSpan())
{
minSpanIdx = col;
}
}
}
if (col >= colCount) //no cell with rowSpan = 1 was found, getting the height of the cell with min rowSpan
{
rowHeight = row.get(minSpanIdx).getHeight();
}
return rowHeight;
}
/**
* This static method calculates all the X cuts for a list of pages.
*
* @param jasperPrint
* The JasperPrint document.
* @param startPageIndex
* The first page to consider.
* @param endPageIndex
* The last page to consider.
* @param offsetX
* horizontal element position offset
*/
public static CutsInfo calculateXCuts(ExporterNature nature, JasperPrint jasperPrint, int startPageIndex, int endPageIndex, int offsetX)
{
CutsInfo xCuts = new CutsInfo();
List pages = jasperPrint.getPages();
for (int pageIndex = startPageIndex; pageIndex <= endPageIndex; pageIndex++)
{
JRPrintPage page = pages.get(pageIndex);
addXCuts(nature, page.getElements(), offsetX, xCuts);
}
// add a cut at the page width if there are not parts and if no element goes beyond the page width
if (!jasperPrint.hasParts())
{
int width = jasperPrint.getPageWidth();
int lastCut = xCuts.getLastCutOffset();
if (lastCut < width)
{
xCuts.addCutOffset(width);
}
}
return xCuts;
}
/**
* This static method calculates all the X cuts for a list of pages.
*
* @param pages
* The list of pages.
* @param startPageIndex
* The first page to consider.
* @param endPageIndex
* The last page to consider.
* @param width
* The page width
* @param offsetX
* horizontal element position offset
* @deprecated Replaced by {@link #calculateXCuts(ExporterNature, JasperPrint, int, int, int)}.
*/
public static CutsInfo calculateXCuts(ExporterNature nature, List pages, int startPageIndex, int endPageIndex, int width, int offsetX)
{
CutsInfo xCuts = new CutsInfo();
for (int pageIndex = startPageIndex; pageIndex <= endPageIndex; pageIndex++)
{
JRPrintPage page = pages.get(pageIndex);
addXCuts(nature, page.getElements(), offsetX, xCuts);
}
// add a cut at the page width if no element goes beyond the width
int lastCut = xCuts.getLastCutOffset();
if (lastCut < width)
{
xCuts.addCutOffset(width);
}
return xCuts;
}
/**
* This static method calculates the X cuts for a list of print elements and
* stores them in the list indicated by the xCuts parameter.
*
* @param elementsList
* The list of elements to be used to determine the X cuts.
* @param elementOffsetX
* horizontal element position offset
* @param xCuts
* The list to which the X cuts are to be added.
*/
protected static void addXCuts(ExporterNature nature, List elementsList, int elementOffsetX, CutsInfo xCuts)
{
for (Iterator it = elementsList.iterator(); it.hasNext();)
{
JRPrintElement element = it.next();
if (nature.isToExport(element))
{
xCuts.addCutOffset(element.getX() + elementOffsetX);
xCuts.addCutOffset(element.getX() + element.getWidth() + elementOffsetX);
if (element instanceof JRPrintFrame)
{
JRPrintFrame frame = (JRPrintFrame) element;
addXCuts(
nature,
frame.getElements(),
element.getX() + elementOffsetX + frame.getLineBox().getLeftPadding(),
xCuts
);
}
nature.setXProperties(xCuts.getPropertiesMap(), element);
}
}
}
/**
*
*/
protected static class BoxKey
{
final JRLineBox box;
final JRLineBox cellBox;
final boolean left;
final boolean right;
final boolean top;
final boolean bottom;
final int hashCode;
BoxKey(JRLineBox box, JRLineBox cellBox, boolean left, boolean right, boolean top, boolean bottom)
{
this.box = box;
this.cellBox = cellBox;
this.left = left;
this.right = right;
this.top = top;
this.bottom = bottom;
int hash = box.hashCode();
if (cellBox != null)
{
hash = 31*hash + cellBox.hashCode();
}
hash = 31*hash + (left ? 1231 : 1237);
hash = 31*hash + (right ? 1231 : 1237);
hash = 31*hash + (top ? 1231 : 1237);
hash = 31*hash + (bottom ? 1231 : 1237);
hashCode = hash;
}
@Override
public boolean equals(Object obj)
{
if (obj == this)
{
return true;
}
BoxKey b = (BoxKey) obj;
return b.box.equals(box) &&
(b.cellBox == null ? cellBox == null : (cellBox != null && b.cellBox.equals(cellBox))) &&
b.left == left && b.right == right && b.top == top && b.bottom == bottom;
}
@Override
public int hashCode()
{
return hashCode;
}
}
}