/* * 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 - 2013 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.ClassicEngineBoot; 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.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.model.table.TableCellRenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.table.TableColumnGroupNode; import org.pentaho.reporting.engine.classic.core.layout.model.table.TableRenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.table.TableRowRenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.table.TableSectionRenderBox; import org.pentaho.reporting.engine.classic.core.layout.process.IterateStructuralProcessStep; import org.pentaho.reporting.engine.classic.core.layout.process.util.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.ShapeDrawable; import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility; import org.pentaho.reporting.libraries.base.util.FastStack; import org.pentaho.reporting.libraries.resourceloader.factory.drawable.DrawableWrapper; 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; private FastStack<RenderBox> parents; private boolean fastCellBackgroundProducerMode; public CellBackgroundProducer( final boolean ellipseAsRectangle, final boolean unalignedPagebands ) { this.ellipseAsRectangle = ellipseAsRectangle; this.unalignedPagebands = unalignedPagebands; this.lookupRectangle = new TableRectangle(); this.fastCellBackgroundProducerMode = "true".equals( ClassicEngineBoot.getInstance().getGlobalConfig().getConfigProperty( "org.pentaho.reporting.engine.classic.core.modules.output.table.base.FastCellBackgroundProducer" ) ); } public CellBackground getBackgroundAt( final LogicalPageBox pageBox, final SheetLayout sheetLayout, final int gridX, final int gridY, final boolean computeAttributes, final CellMarker.SectionType sectionType ) { return computeBackground( pageBox, sheetLayout, gridX, gridY, 1, 1, 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 CellMarker.SectionType 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 ); startProcessing( logicalPageBox.getRepeatFooterArea() ); startProcessing( logicalPageBox.getFooterArea() ); return retval; } switch ( sectionType ) { case TYPE_HEADER: { contentShift = 0; startProcessing( headerArea ); return retval; } case TYPE_NORMALFLOW: { final BlockRenderBox contentArea = logicalPageBox.getContentArea(); final long contentStart = headerArea.getHeight() + contentArea.getY(); contentShift = contentStart - logicalPageBox.getPageOffset(); startProcessing( contentArea ); return retval; } case TYPE_REPEAT_FOOTER: { final BlockRenderBox contentArea = logicalPageBox.getContentArea(); final long contentStart = headerArea.getHeight() + contentArea.getY(); final BlockRenderBox footerArea = logicalPageBox.getRepeatFooterArea(); contentShift = contentStart + contentArea.getHeight(); startProcessing( footerArea ); return retval; } case TYPE_FOOTER: { final BlockRenderBox contentArea = logicalPageBox.getContentArea(); final long contentStart = headerArea.getHeight() + contentArea.getY(); final BlockRenderBox repeatFooterArea = logicalPageBox.getRepeatFooterArea(); final BlockRenderBox footerArea = logicalPageBox.getFooterArea(); contentShift = contentStart + contentArea.getHeight() + repeatFooterArea.getHeight(); startProcessing( footerArea ); return retval; } case TYPE_INVALID: return null; default: { throw new IllegalStateException(); } } } public CellBackground getBackgroundForBox( final LogicalPageBox logicalPageBox, final SheetLayout sheetLayout, final int gridX, final int gridY, final int gridWidth, final int gridHeight, final boolean collectAttributes, final CellMarker.SectionType sectionType, final RenderBox contentBox ) { if ( fastCellBackgroundProducerMode == false ) { return computeBackground( logicalPageBox, sheetLayout, gridX, gridY, gridWidth, gridHeight, collectAttributes, 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; } else { switch ( sectionType ) { case TYPE_HEADER: { contentShift = 0; break; } case TYPE_NORMALFLOW: { final BlockRenderBox contentArea = logicalPageBox.getContentArea(); final long contentStart = headerArea.getHeight() + contentArea.getY(); contentShift = contentStart - logicalPageBox.getPageOffset(); break; } case TYPE_REPEAT_FOOTER: { final BlockRenderBox contentArea = logicalPageBox.getContentArea(); final long contentStart = headerArea.getHeight() + contentArea.getY(); contentShift = contentStart + contentArea.getHeight(); break; } case TYPE_FOOTER: { final BlockRenderBox contentArea = logicalPageBox.getContentArea(); final long contentStart = headerArea.getHeight() + contentArea.getY(); final BlockRenderBox repeatFooterArea = logicalPageBox.getRepeatFooterArea(); contentShift = contentStart + contentArea.getHeight() + repeatFooterArea.getHeight(); break; } case TYPE_INVALID: return null; default: { throw new IllegalStateException(); } } } if ( parents == null ) { parents = new FastStack<RenderBox>(); } else { parents.clear(); } RenderBox p = contentBox; boolean seenSectionBox = false; RenderBox lastProcessed = null; while ( p != null ) { if ( p.getStaticBoxLayoutProperties().isSectionContext() ) { seenSectionBox = true; } if ( seenSectionBox ) { parents.push( p ); } else { lastProcessed = p; } p = p.getParent(); } boolean receivedNoBackground = false; while ( parents.isEmpty() == false ) { final RenderBox p2 = parents.pop(); if ( startBox( p2 ) == false ) { receivedNoBackground = true; break; } } if ( receivedNoBackground == false && lastProcessed != null ) { startProcessing( lastProcessed ); } return retval; } 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 = sheetLayout.getTableBoundsWithCache( node.getX(), node.getY() + contentShift, node.getWidth(), node .getHeight(), new TableRectangle( -1, -1, -1, -1 ) ); } else { if ( renderBoxState.getBackgroundDefinitionAge() != node.getCachedAge() ) { 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, node.getCachedAge() ); 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 ( edge == null ) { return; } 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 ) { return startBox( box ); } protected boolean startInlineBox( final InlineRenderBox box ) { return startBox( box ); } protected boolean startCanvasBox( final CanvasRenderBox box ) { return startBox( box ); } protected boolean startOtherBox( final RenderBox box ) { return startBox( box ); } protected boolean startTableCellBox( final TableCellRenderBox box ) { return startBox( box ); } protected boolean startTableRowBox( final TableRowRenderBox box ) { return startBox( box ); } protected boolean startTableSectionBox( final TableSectionRenderBox box ) { return startBox( box ); } protected boolean startTableColumnGroupBox( final TableColumnGroupNode box ) { return true; } protected boolean startTableBox( final TableRenderBox box ) { return startBox( box ); } protected boolean startAutoBox( final RenderBox box ) { return true; } private boolean startBox( 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 startBox( 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; } }