/*!
* 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) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.layout.process;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.ElementAlignment;
import org.pentaho.reporting.engine.classic.core.InvalidReportStateException;
import org.pentaho.reporting.engine.classic.core.layout.ModelPrinter;
import org.pentaho.reporting.engine.classic.core.layout.model.BlockRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.LayoutNodeTypes;
import org.pentaho.reporting.engine.classic.core.layout.model.LogicalPageBox;
import org.pentaho.reporting.engine.classic.core.layout.model.PageBreakPositionList;
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.context.StaticBoxLayoutProperties;
import org.pentaho.reporting.engine.classic.core.layout.process.util.BasePaginationTableState;
import org.pentaho.reporting.engine.classic.core.layout.process.util.PaginationShiftState;
import org.pentaho.reporting.engine.classic.core.style.ElementStyleKeys;
/**
* A helper class that contains generic methods that would distract me from the actual pagination logic.
*/
public final class PaginationStepLib {
private static final Log logger = LogFactory.getLog( PaginationStepLib.class );
private PaginationStepLib() {
}
public static void configureBreakUtility( final PageBreakPositionList breakUtility, final LogicalPageBox pageBox,
final long[] allCurrentBreaks, final long reservedHeight, final long lastBreakLocal ) {
final PageBreakPositionList allPreviousBreak = pageBox.getAllVerticalBreaks();
breakUtility.copyFrom( allPreviousBreak );
final long pageOffset = pageBox.getPageOffset();
final long headerHeight = pageBox.getHeaderArea().getHeight();
// Then add all new breaks (but take the header and footer-size into account) ..
if ( allCurrentBreaks.length == 1 ) {
breakUtility.addMajorBreak( pageOffset, headerHeight );
breakUtility.addMajorBreak( ( lastBreakLocal - reservedHeight ) + pageOffset, headerHeight );
} else { // more than one physical page; therefore header and footer are each on a separate canvas ..
breakUtility.addMajorBreak( pageOffset, headerHeight );
final int breakCount = allCurrentBreaks.length - 1;
for ( int i = 1; i < breakCount; i++ ) {
final long aBreak = allCurrentBreaks[i];
breakUtility.addMinorBreak( pageOffset + ( aBreak - headerHeight ) );
}
breakUtility.addMajorBreak( pageOffset + ( lastBreakLocal - reservedHeight ), headerHeight );
}
}
public static void assertProgress( final LogicalPageBox pageBox ) {
final RenderNode lastChild = pageBox.getLastChild();
if ( lastChild != null ) {
final long lastChildY2 = lastChild.getY() + lastChild.getHeight();
if ( lastChildY2 < pageBox.getHeight() ) {
// ModelPrinter.print(pageBox);
throw new IllegalStateException( "Assertation failed: Pagination did not proceed: " + lastChildY2 + " < "
+ pageBox.getHeight() );
}
}
}
public static long restrictPageAreaHeights( final LogicalPageBox pageBox, final long[] allCurrentBreaks ) {
final BlockRenderBox headerArea = pageBox.getHeaderArea();
final long headerHeight = Math.min( headerArea.getHeight(), allCurrentBreaks[0] );
headerArea.setHeight( headerHeight );
final BlockRenderBox footerArea = pageBox.getFooterArea();
final BlockRenderBox repeatFooterArea = pageBox.getRepeatFooterArea();
if ( allCurrentBreaks.length > 1 ) {
final long lastBreakLocal = allCurrentBreaks[allCurrentBreaks.length - 1];
final long lastPageHeight = lastBreakLocal - allCurrentBreaks[allCurrentBreaks.length - 2];
final long footerHeight = Math.min( footerArea.getHeight(), lastPageHeight );
footerArea.setHeight( footerHeight );
final long repeatFooterHeight = Math.min( repeatFooterArea.getHeight(), lastPageHeight );
repeatFooterArea.setHeight( repeatFooterHeight );
}
final long footerHeight = footerArea.getHeight();
final long repeatFooterHeight = repeatFooterArea.getHeight();
// Assertion: Make sure that we do not run into a infinite loop..
return headerHeight + repeatFooterHeight + footerHeight;
}
public static void assertBlockPosition( final RenderBox box, final long shift ) {
if ( box.getLayoutNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE_SECTION ) {
// no point in testing table-sections, as the header will be an out-of-order band.
return;
}
final boolean error;
final long expectedYPos;
if ( box.getPrev() != null ) {
error = true;
expectedYPos = box.getPrev().getY() + box.getPrev().getHeight();
} else {
if ( box.getParent() != null ) {
error = false;
expectedYPos = box.getParent().getY();
final Object parentVAlignment = box.getParent().getStyleSheet().getStyleProperty( ElementStyleKeys.VALIGNMENT );
if ( parentVAlignment != null && ElementAlignment.TOP.equals( parentVAlignment ) == false ) {
return;
}
} else {
error = true;
expectedYPos = 0;
}
}
final long realY = box.getY() + shift;
if ( realY != expectedYPos ) {
final long additionalShift = expectedYPos - realY;
final long realShift = shift + additionalShift;
if ( error ) {
ModelPrinter.INSTANCE.print( box );
ModelPrinter.INSTANCE.print( ModelPrinter.getRoot( box ) );
throw new InvalidReportStateException( String.format( "Assert: Shift is not as expected: "
+ "realY=%d != expectation=%d; Shift=%d; AdditionalShift=%d; RealShift=%d", realY, expectedYPos, shift,
additionalShift, realShift ) );
} else {
if ( logger.isDebugEnabled() ) {
logger.debug( String.format( "Assert: Shift is not as expected: realY=%d != expectation=%d; Shift=%d; "
+ "AdditionalShift=%d; RealShift=%d (False positive if block box has valign != TOP", realY, expectedYPos,
shift, additionalShift, realShift ) );
}
}
}
}
/**
* Computes the height that will be required on this page to display at least some parts of the box.
*
* @param box
* the box for which the height is computed
* @return the height in micro-points.
*/
public static long computeNonBreakableBoxHeight( final RenderBox box, final PaginationShiftState shiftState,
final BasePaginationTableState tableState ) {
// must return the reserved space starting from box's y position.
final long widowSize = getWidowConstraint( box, shiftState, tableState );
final StaticBoxLayoutProperties sblp = box.getStaticBoxLayoutProperties();
if ( sblp.isAvoidPagebreakInside()
&& box.getRestrictFinishedClearOut() != RenderBox.RestrictFinishClearOut.RESTRICTED ) {
return Math.max( widowSize, box.getHeight() );
}
final int nodeType = box.getLayoutNodeType();
if ( ( nodeType & LayoutNodeTypes.MASK_BOX_BLOCK ) != LayoutNodeTypes.MASK_BOX_BLOCK ) {
// Canvas, row or inline boxes have no notion of lines, and therefore they cannot have orphans and widows.
return widowSize;
}
if ( isOrphanConstraintNeeded( box, shiftState, tableState ) == false ) {
return widowSize;
}
final long orphanHeight = box.getOrphanConstraintSize();
if ( widowSize + orphanHeight > box.getHeight() ) {
// if the widows and orphan areas overlap, then the box becomes non-breakable.
return Math.max( widowSize, box.getHeight() );
}
return Math.max( orphanHeight, widowSize );
}
/**
* Widow constraint evaluation is skipped if
* <p/>
* (a) the box has a widow/orphan-parent defining that sits on the current page's page-offset and (b) this parent's
* orphan-restricted pagebreak-exclusion zone includes this box.
*
* @param box
* @param shiftState
* @param tableState
* @return
*/
private static boolean isOrphanConstraintNeeded( final RenderBox box, final PaginationShiftState shiftState,
final BasePaginationTableState tableState ) {
final long boxY = box.getY() + shiftState.getShiftForNextChild();
final long pageOffset = tableState.getPageOffset( boxY );
if ( pageOffset == boxY ) {
// no need to set a widow constraint, we are not shifting anyway ..
return false;
}
if ( box.getRestrictFinishedClearOut() != RenderBox.RestrictFinishClearOut.RESTRICTED ) {
return false;
}
if ( isBoxInsideParentOrphanZoneOnThisPage( box, pageOffset, boxY ) ) {
return false;
}
return true;
}
private static boolean isBoxInsideParentOrphanZoneOnThisPage( final RenderBox box, final long pageOffset,
final long boxYShifted ) {
RenderBox parent = box.getParent();
while ( parent != null ) {
if ( parent.getRestrictFinishedClearOut() == RenderBox.RestrictFinishClearOut.UNRESTRICTED ) {
break;
}
if ( parent.getY() < pageOffset ) {
// once the parent is sitting on the previous page, we no longer need to ask it ..
break;
}
if ( parent.getOrphanConstraintSize() == 0 ) {
parent = parent.getParent();
continue;
}
final long constraintBoundary = parent.getY() + parent.getOrphanConstraintSize();
if ( constraintBoundary > boxYShifted ) {
// this parent is not relevant.
return true;
}
parent = parent.getParent();
}
return false;
}
public static long getWidowConstraint( final RenderBox box, final PaginationShiftState shiftState,
final BasePaginationTableState tableState ) {
if ( box.isWidowBox() == false ) {
return 0;
}
final long boxY = box.getY() + shiftState.getShiftForNextChild();
long retval = 0;
RenderBox parent = box.getParent();
final long pageOffset = tableState.getPageOffset();
if ( pageOffset == boxY ) {
// no need to set a widow constraint, we are not shifting anyway ..
return 0;
}
while ( parent != null ) {
if ( parent.getRestrictFinishedClearOut() == RenderBox.RestrictFinishClearOut.UNRESTRICTED ) {
break;
}
if ( parent.getWidowConstraintSize() == 0 ) {
parent = parent.getParent();
continue;
}
final long y2 = parent.getY2();
final long constraintBoundary;
if ( parent.getY() < pageOffset ) {
constraintBoundary = y2 - Math.max( 0, parent.getWidowConstraintSize() );
} else {
constraintBoundary = y2 - Math.max( 0, parent.getWidowConstraintSizeWithKeepTogether() );
}
if ( constraintBoundary == boxY ) {
retval = Math.max( retval, y2 - boxY );
}
parent = parent.getParent();
}
return retval;
}
}