/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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 Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2009 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.modules.output.table.base;
import java.awt.Color;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import org.pentaho.reporting.engine.classic.core.layout.model.BlockRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.Border;
import org.pentaho.reporting.engine.classic.core.layout.model.BorderCorner;
import org.pentaho.reporting.engine.classic.core.layout.model.BorderEdge;
import org.pentaho.reporting.engine.classic.core.layout.model.CanvasRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.InlineRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.LogicalPageBox;
import org.pentaho.reporting.engine.classic.core.layout.model.ParagraphRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderNode;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableReplacedContent;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableReplacedContentBox;
import org.pentaho.reporting.engine.classic.core.layout.model.context.BoxDefinition;
import org.pentaho.reporting.engine.classic.core.layout.process.IterateStructuralProcessStep;
import org.pentaho.reporting.engine.classic.core.layout.process.ProcessUtility;
import org.pentaho.reporting.engine.classic.core.metadata.ElementType;
import org.pentaho.reporting.engine.classic.core.style.ElementStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.StyleSheet;
import org.pentaho.reporting.engine.classic.core.util.IntegerCache;
import org.pentaho.reporting.engine.classic.core.util.ShapeDrawable;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility;
import org.pentaho.reporting.libraries.resourceloader.factory.drawable.DrawableWrapper;
/**
* Todo: Document Me
*
* @author Thomas Morgner
*/
public class CellBackgroundProducer extends IterateStructuralProcessStep
{
private static final int BACKGROUND_NONE = 0;
private static final int BACKGROUND_AREA = 1;
private static final int BACKGROUND_TOP = 2;
private static final int BACKGROUND_LEFT = 4;
private static final int BACKGROUND_BOTTOM = 8;
private static final int BACKGROUND_RIGHT = 16;
private TableRectangle lookupRectangle;
private int gridX;
private int gridY;
private boolean collectAttributes;
private SheetLayout sheetLayout;
private int gridX2;
private int gridY2;
private CellBackground retval;
private long resolvedX;
private long resolvedY;
private boolean ellipseAsRectangle;
private boolean unalignedPagebands;
private long contentShift;
public CellBackgroundProducer(final boolean ellipseAsRectangle,
final boolean unalignedPagebands)
{
this.ellipseAsRectangle = ellipseAsRectangle;
this.unalignedPagebands = unalignedPagebands;
this.lookupRectangle = new TableRectangle();
}
public CellBackground getBackgroundAt(final LogicalPageBox pageBox,
final SheetLayout sheetLayout,
final int gridX,
final int gridY,
final boolean computeAttributes,
final int sectionType)
{
return computeBackground(pageBox, sheetLayout, gridX, gridY, 1, 1, computeAttributes, sectionType);
}
public CellBackground getBackgroundAt(final LogicalPageBox pageBox,
final SheetLayout sheetLayout,
final int gridX,
final int gridY,
final int gridWidth,
final int gridHeight,
final boolean computeAttributes,
final int sectionType)
{
return computeBackground
(pageBox, sheetLayout, gridX, gridY, gridWidth, gridHeight, computeAttributes, sectionType);
}
private CellBackground computeBackground(final LogicalPageBox logicalPageBox,
final SheetLayout sheetLayout,
final int gridX,
final int gridY,
final int gridWidth,
final int gridHeight,
final boolean collectAttributes,
final int sectionType)
{
if (logicalPageBox == null)
{
throw new NullPointerException();
}
if (sheetLayout == null)
{
throw new NullPointerException();
}
this.sheetLayout = sheetLayout;
this.collectAttributes = collectAttributes;
this.retval = null;
initFromPosition(gridX, gridY, gridWidth, gridHeight);
final BlockRenderBox headerArea = logicalPageBox.getHeaderArea();
if (unalignedPagebands == false)
{
contentShift = 0;
startProcessing(headerArea);
startProcessing(logicalPageBox);
final BlockRenderBox footerArea = logicalPageBox.getFooterArea();
startProcessing(footerArea);
return retval;
}
switch (sectionType)
{
case CellMarker.TYPE_HEADER:
{
contentShift = 0;
startProcessing(headerArea);
return retval;
}
case CellMarker.TYPE_NORMALFLOW:
{
final BlockRenderBox contentArea = logicalPageBox.getContentArea();
final long contentStart = headerArea.getY() + headerArea.getHeight() + contentArea.getY();
contentShift = contentStart - logicalPageBox.getPageOffset();
startProcessing(contentArea);
return retval;
}
case CellMarker.TYPE_FOOTER:
{
final BlockRenderBox contentArea = logicalPageBox.getContentArea();
final long contentStart = headerArea.getY() + headerArea.getHeight() + contentArea.getY();
final BlockRenderBox footerArea = logicalPageBox.getFooterArea();
contentShift = contentStart + contentArea.getHeight() + footerArea.getY();
startProcessing(footerArea);
return retval;
}
case CellMarker.TYPE_INVALID:
return null;
default:
{
throw new IllegalStateException();
}
}
}
private void initFromPosition(final int gridX, final int gridY, final int gridWidth, final int gridHeight)
{
this.resolvedX = sheetLayout.getXPosition(gridX);
this.resolvedY = sheetLayout.getYPosition(gridY);
this.gridX = gridX;
this.gridY = gridY;
this.gridX2 = gridX + gridWidth;
this.gridY2 = gridY + gridHeight;
}
private int computeBackground(final RenderBox node)
{
final Object state = node.getTableExportState();
final TableExportRenderBoxState renderBoxState;
if (state instanceof TableExportRenderBoxState)
{
renderBoxState = (TableExportRenderBoxState) state;
}
else
{
renderBoxState = new TableExportRenderBoxState();
node.setTableExportState(renderBoxState);
}
TableRectangle hint = renderBoxState.getCellBackgroundHint();
if (hint == null)
{
hint = new TableRectangle(-1, -1, -1, -1);
}
hint = sheetLayout.getTableBoundsWithCache(node.getX(), node.getY() + contentShift, node.getWidth(), node.getHeight(), hint);
final int x1 = hint.getX1();
final int y1 = hint.getY1();
final int x2 = hint.getX2();
final int y2 = hint.getY2();
final int retval = computeBackgroundHint(x1, y1, x2, y2);
renderBoxState.setCellBackgroundHint(hint, contentShift);
return retval;
}
private int computeBackground(final long x, final long y, final long width, final long height)
{
lookupRectangle = sheetLayout.getTableBounds(x, y + contentShift, width, height, lookupRectangle);
final int x1 = lookupRectangle.getX1();
final int y1 = lookupRectangle.getY1();
final int x2 = lookupRectangle.getX2();
final int y2 = lookupRectangle.getY2();
return computeBackgroundHint(x1, y1, x2, y2);
}
private int computeBackgroundHint(final int x1, final int y1, final int x2, final int y2)
{
// a node is a background if it fully covers the given grid-area. A node that only covers the area
// partially is not a background.
// for lines: At least the full width or full height must be covered.
// for background colors: The full area must be covered.
int retval = BACKGROUND_NONE;
if (x1 <= gridX && x2 >= gridX2)
{
// covers full width ..
// could be a horizontal line or a full area background.
if (y1 <= gridY && y2 >= gridY2)
{
// full area background
retval |= BACKGROUND_AREA;
}
if (y1 == gridY)
{
retval |= BACKGROUND_TOP;
}
if (y2 == gridY2)
{
retval |= BACKGROUND_BOTTOM;
}
}
if (y1 <= gridY && y2 >= gridY2)
{
if (x1 == gridX)
{
retval |= BACKGROUND_LEFT;
}
if (x2 == gridX2)
{
retval |= BACKGROUND_RIGHT;
}
}
return retval;
}
protected void processRenderableContent(final RenderableReplacedContentBox box)
{
final int backgroundHint = computeBackground(box);
if (backgroundHint == BACKGROUND_NONE)
{
return;
}
retval = applyBorder(box, retval, backgroundHint);
if ((backgroundHint & BACKGROUND_AREA) == BACKGROUND_AREA)
{
retval = applyBackground(box, retval);
retval = applyAnchor(box, contentShift, resolvedX, resolvedY, retval);
retval = applyElementType(box, contentShift, resolvedX, resolvedY, retval);
if (collectAttributes)
{
retval = applyAttributes(box, contentShift, resolvedX, resolvedY, retval);
}
}
computeLegacyBackground(box);
}
private void computeLegacyBackground(final RenderableReplacedContentBox node)
{
final BoxDefinition sblp = node.getBoxDefinition();
final long nodeX = node.getX() + sblp.getPaddingLeft();
final long nodeY = node.getY() + sblp.getPaddingTop();
final long nodeWidth = node.getWidth() - sblp.getPaddingLeft() - sblp.getPaddingRight();
final long nodeHeight = node.getHeight() - sblp.getPaddingTop() - sblp.getPaddingBottom();
final int backgroundHint = computeBackground(nodeX, nodeY, nodeWidth, nodeHeight);
if (backgroundHint == BACKGROUND_NONE)
{
return;
}
final RenderableReplacedContent rpc = node.getContent();
final Object rawContentObject = rpc.getRawObject();
if (rawContentObject instanceof DrawableWrapper == false)
{
return;
}
final DrawableWrapper wrapper = (DrawableWrapper) rawContentObject;
final Object rawbackend = wrapper.getBackend();
if (rawbackend instanceof ShapeDrawable == false)
{
return;
}
final ShapeDrawable drawable = (ShapeDrawable) rawbackend;
final Shape shape = drawable.getShape();
final StyleSheet styleSheet = node.getStyleSheet();
final boolean draw = styleSheet.getBooleanStyleProperty(ElementStyleKeys.DRAW_SHAPE);
if (draw && shape instanceof Line2D)
{
final Line2D line = (Line2D) shape;
final boolean vertical = line.getX1() == line.getX2();
final boolean horizontal = line.getY1() == line.getY2();
if (vertical && horizontal)
{
// not a valid line ..
return;
}
if (vertical == false && horizontal == false)
{
// not a valid line ..
return;
}
if (retval == null)
{
retval = new CellBackground();
}
final BorderEdge edge = ProcessUtility.produceBorderEdge(styleSheet);
if (vertical)
{
if (line.getX1() == 0)
{
if ((backgroundHint & BACKGROUND_LEFT) == BACKGROUND_LEFT)
{
retval.setLeft(edge);
}
else if ((backgroundHint & BACKGROUND_RIGHT) == BACKGROUND_RIGHT)
{
final RenderBox nodeParent = node.getParent();
if (nodeParent != null && (nodeParent.getX() + nodeParent.getWidth()) == (nodeX + nodeWidth))
{
retval.setRight(edge);
}
}
}
else
{
if ((backgroundHint & BACKGROUND_RIGHT) == BACKGROUND_RIGHT)
{
retval.setRight(edge);
}
}
}
else
{
if (line.getY1() == 0)
{
if ((backgroundHint & BACKGROUND_TOP) == BACKGROUND_TOP)
{
retval.setTop(edge);
}
else if ((backgroundHint & BACKGROUND_BOTTOM) == BACKGROUND_BOTTOM)
{
final RenderBox nodeParent = node.getParent();
if (nodeParent != null && (nodeParent.getY() + nodeParent.getHeight()) == (nodeY + nodeHeight))
{
retval.setBottom(edge);
}
}
}
else
{
if ((backgroundHint & BACKGROUND_BOTTOM) == BACKGROUND_BOTTOM)
{
retval.setBottom(edge);
}
}
}
return;
}
final boolean fill = styleSheet.getBooleanStyleProperty(ElementStyleKeys.FILL_SHAPE);
if (draw == false && fill == false)
{
return;
}
if (shape instanceof Rectangle2D ||
(ellipseAsRectangle && shape instanceof Ellipse2D))
{
if (retval == null)
{
retval = new CellBackground();
}
if (draw)
{
// the beast has a border ..
final BorderEdge edge = ProcessUtility.produceBorderEdge(styleSheet);
if (edge != null)
{
if ((backgroundHint & BACKGROUND_TOP) == BACKGROUND_TOP)
{
retval.setTop(edge);
}
if ((backgroundHint & BACKGROUND_LEFT) == BACKGROUND_LEFT)
{
retval.setLeft(edge);
}
if ((backgroundHint & BACKGROUND_BOTTOM) == BACKGROUND_BOTTOM)
{
retval.setBottom(edge);
}
if ((backgroundHint & BACKGROUND_RIGHT) == BACKGROUND_RIGHT)
{
retval.setRight(edge);
}
}
}
if (fill && ((backgroundHint & BACKGROUND_AREA) == BACKGROUND_AREA))
{
final Color color = (Color) styleSheet.getStyleProperty(ElementStyleKeys.FILL_COLOR);
if (color != null)
{
retval.addBackground(color);
}
else
{
retval.addBackground((Color) styleSheet.getStyleProperty(ElementStyleKeys.PAINT));
}
}
return;
}
if (shape instanceof RoundRectangle2D)
{
final RoundRectangle2D rr = (RoundRectangle2D) shape;
if (retval == null)
{
retval = new CellBackground();
}
if (draw)
{
// the beast has a border ..
final BorderEdge edge = ProcessUtility.produceBorderEdge(styleSheet);
if (edge != null)
{
if ((backgroundHint & BACKGROUND_TOP) == BACKGROUND_TOP)
{
retval.setTop(edge);
}
if ((backgroundHint & BACKGROUND_LEFT) == BACKGROUND_LEFT)
{
retval.setLeft(edge);
}
if ((backgroundHint & BACKGROUND_BOTTOM) == BACKGROUND_BOTTOM)
{
retval.setBottom(edge);
}
if ((backgroundHint & BACKGROUND_RIGHT) == BACKGROUND_RIGHT)
{
retval.setRight(edge);
}
}
}
if (fill && ((backgroundHint & BACKGROUND_AREA) == BACKGROUND_AREA))
{
final Color color = (Color) styleSheet.getStyleProperty(ElementStyleKeys.FILL_COLOR);
if (color != null)
{
retval.addBackground(color);
}
else
{
retval.addBackground((Color) styleSheet.getStyleProperty(ElementStyleKeys.PAINT));
}
}
final long arcHeight = StrictGeomUtility.toInternalValue(rr.getArcHeight());
final long arcWidth = StrictGeomUtility.toInternalValue(rr.getArcWidth());
if (arcHeight > 0 && arcWidth > 0)
{
final BorderCorner bc = new BorderCorner(arcWidth, arcHeight);
if ((backgroundHint & BACKGROUND_TOP) == BACKGROUND_TOP)
{
if ((backgroundHint & BACKGROUND_LEFT) == BACKGROUND_LEFT)
{
retval.setTopLeft(bc);
}
if ((backgroundHint & BACKGROUND_RIGHT) == BACKGROUND_RIGHT)
{
retval.setTopRight(bc);
}
}
if ((backgroundHint & BACKGROUND_BOTTOM) == BACKGROUND_BOTTOM)
{
if ((backgroundHint & BACKGROUND_LEFT) == BACKGROUND_LEFT)
{
retval.setBottomLeft(bc);
}
if ((backgroundHint & BACKGROUND_RIGHT) == BACKGROUND_RIGHT)
{
retval.setBottomRight(bc);
}
}
}
}
}
protected boolean startBlockBox(final BlockRenderBox box)
{
final int backgroundHint = computeBackground(box);
if (backgroundHint == BACKGROUND_NONE)
{
return false;
}
retval = applyBorder(box, retval, backgroundHint);
if ((backgroundHint & BACKGROUND_AREA) == BACKGROUND_AREA)
{
retval = applyBackground(box, retval);
retval = applyAnchor(box, contentShift, resolvedX, resolvedY, retval);
retval = applyElementType(box, contentShift, resolvedX, resolvedY, retval);
if (collectAttributes)
{
retval = applyAttributes(box, contentShift, resolvedX, resolvedY, retval);
}
return true;
}
return true;
}
protected boolean startInlineBox(final InlineRenderBox box)
{
final int backgroundHint = computeBackground(box);
if (backgroundHint == BACKGROUND_NONE)
{
return false;
}
retval = applyBorder(box, retval, backgroundHint);
if ((backgroundHint & BACKGROUND_AREA) == BACKGROUND_AREA)
{
retval = applyBackground(box, retval);
retval = applyAnchor(box, contentShift, resolvedX, resolvedY, retval);
retval = applyElementType(box, contentShift, resolvedX, resolvedY, retval);
if (collectAttributes)
{
retval = applyAttributes(box, contentShift, resolvedX, resolvedY, retval);
}
return true;
}
return true;
}
protected boolean startCanvasBox(final CanvasRenderBox box)
{
final int backgroundHint = computeBackground(box);
if (backgroundHint == BACKGROUND_NONE)
{
return false;
}
retval = applyBorder(box, retval, backgroundHint);
if ((backgroundHint & BACKGROUND_AREA) == BACKGROUND_AREA)
{
retval = applyBackground(box, retval);
retval = applyAnchor(box, contentShift, resolvedX, resolvedY, retval);
retval = applyElementType(box, contentShift, resolvedX, resolvedY, retval);
if (collectAttributes)
{
retval = applyAttributes(box, contentShift, resolvedX, resolvedY, retval);
}
return true;
}
return true;
}
protected boolean startOtherBox(final RenderBox box)
{
final int backgroundHint = computeBackground(box);
if (backgroundHint == BACKGROUND_NONE)
{
return false;
}
retval = applyBorder(box, retval, backgroundHint);
if ((backgroundHint & BACKGROUND_AREA) == BACKGROUND_AREA)
{
retval = applyBackground(box, retval);
retval = applyAnchor(box, contentShift, resolvedX, resolvedY, retval);
retval = applyElementType(box, contentShift, resolvedX, resolvedY, retval);
if (collectAttributes)
{
retval = applyAttributes(box, contentShift, resolvedX, resolvedY, retval);
}
return true;
}
return true;
}
protected boolean startRowBox(final RenderBox box)
{
return startOtherBox(box);
}
protected void processParagraphChilds(final ParagraphRenderBox box)
{
// no need for that. Paragraph contents cannot form a cell-background.
}
private static CellBackground applyAnchor(final RenderBox content,
final long contentShift,
final long x,
final long y,
CellBackground retval)
{
if (content.getY() + contentShift != y || content.getX() != x)
{
return retval;
}
final String anchor = (String) content.getStyleSheet().getStyleProperty(ElementStyleKeys.ANCHOR_NAME);
if (anchor != null)
{
if (retval == null)
{
retval = new CellBackground();
}
retval.addAnchor(anchor);
}
return retval;
}
private static CellBackground applyElementType(final RenderBox content,
final long contentShift,
final long x,
final long y,
CellBackground retval)
{
if (content.getY() + contentShift != y || content.getX() != x)
{
return retval;
}
final ElementType anchor = content.getElementType();
if (anchor != null)
{
if (retval == null)
{
retval = new CellBackground();
}
retval.addElementType(anchor);
}
return retval;
}
private static CellBackground applyAttributes(final RenderBox content,
final long contentShift,
final long x,
final long y,
CellBackground retval)
{
if (content.getY() + contentShift != y || content.getX() != x)
{
return retval;
}
if (retval == null)
{
retval = new CellBackground();
}
retval.addAttributes(content.getAttributes());
return retval;
}
private static CellBackground applyBorder(final RenderBox content,
CellBackground retval,
final int backgroundHint)
{
final Border border = content.getBoxDefinition().getBorder();
if (border.isEmpty())
{
return retval;
}
if ((backgroundHint & BACKGROUND_TOP) == BACKGROUND_TOP)
{
final BorderEdge borderEdgeTop = border.getTop();
if (borderEdgeTop.isEmpty() == false)
{
if (retval == null)
{
retval = new CellBackground();
}
retval.setTop(borderEdgeTop);
}
}
if ((backgroundHint & BACKGROUND_LEFT) == BACKGROUND_LEFT)
{
final BorderEdge borderEdgeLeft = border.getLeft();
if (borderEdgeLeft.isEmpty() == false)
{
if (retval == null)
{
retval = new CellBackground();
}
retval.setLeft(borderEdgeLeft);
}
}
if ((backgroundHint & BACKGROUND_BOTTOM) == BACKGROUND_BOTTOM)
{
final BorderEdge borderEdgeBottom = border.getBottom();
if (borderEdgeBottom.isEmpty() == false)
{
if (retval == null)
{
retval = new CellBackground();
}
retval.setBottom(borderEdgeBottom);
}
}
if ((backgroundHint & BACKGROUND_RIGHT) == BACKGROUND_RIGHT)
{
final BorderEdge borderEdgeRight = border.getRight();
if (borderEdgeRight.isEmpty() == false)
{
if (retval == null)
{
retval = new CellBackground();
}
retval.setRight(borderEdgeRight);
}
}
return retval;
}
private static CellBackground applyBackground(final RenderBox content, CellBackground retval)
{
final Color backgroundColor = (Color) content.getStyleSheet().getStyleProperty(ElementStyleKeys.BACKGROUND_COLOR);
if (backgroundColor != null && backgroundColor.getAlpha() > 0)
{
if (retval == null)
{
retval = new CellBackground();
}
retval.addBackground(backgroundColor);
}
return retval;
}
}