/*
* 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.layout.process;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.InvalidReportStateException;
import org.pentaho.reporting.engine.classic.core.filter.types.bands.SubGroupBodyType;
import org.pentaho.reporting.engine.classic.core.layout.model.BreakMarkerRenderBox;
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.PageBreakPositions;
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.RenderLength;
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.model.table.TableSectionRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.process.util.BoxShifter;
import org.pentaho.reporting.engine.classic.core.layout.process.util.InitialPaginationShiftState;
import org.pentaho.reporting.engine.classic.core.layout.process.util.PaginationResult;
import org.pentaho.reporting.engine.classic.core.layout.process.util.PaginationShiftState;
import org.pentaho.reporting.engine.classic.core.layout.process.util.PaginationShiftStatePool;
import org.pentaho.reporting.engine.classic.core.layout.process.util.PaginationTableState;
import org.pentaho.reporting.engine.classic.core.states.ReportStateKey;
import org.pentaho.reporting.libraries.base.util.DebugLog;
/**
* This class uses the concept of shifting to push boxes, which otherwise do not fit on the current page, over the
* page-boundary of the next page.
* <p/>
* We have two shift positions. The normal shift denotes artificial paddings, inserted into the flow where needed to
* move content to the next page. The header-shift is inserted when a repeatable table-header is processed. This header
* reserves a virtual padding area in the infinite-canvas flow to push the next assumed pagebreak to the y2-position
* of the header. A header-shift modifies the pin-position on a box, and modifies where pagebreaks are detected.
*/
@SuppressWarnings("HardCodedStringLiteral")
public final class PaginationStep extends IterateVisualProcessStep
{
private static final Log logger = LogFactory.getLog(PaginationStep.class);
private boolean breakPending;
private FindOldestProcessKeyStep findOldestProcessKeyStep;
private PageBreakPositionList basePageBreakList;
private ReportStateKey visualState;
private BreakMarkerRenderBox breakIndicatorEncountered;
private PaginationTableState paginationTableState;
private PaginationShiftState shiftState;
private PaginationShiftStatePool shiftStatePool;
private long pageOffsetKey;
private long usablePageHeight;
public PaginationStep()
{
findOldestProcessKeyStep = new FindOldestProcessKeyStep();
basePageBreakList = new PageBreakPositionList();
shiftStatePool = new PaginationShiftStatePool();
}
public PaginationResult performPagebreak(final LogicalPageBox pageBox)
{
getEventWatch().start();
getSummaryWatch().start();
try{
PaginationStepLib.assertProgress(pageBox);
if (logger.isDebugEnabled())
{
logger.debug("Start pagination ... " + pageBox.getPageOffset());
}
this.breakIndicatorEncountered = null;
this.visualState = null;
this.pageOffsetKey = pageBox.getPageOffset();
this.shiftState = new InitialPaginationShiftState();
this.breakPending = false;
this.usablePageHeight = Long.MAX_VALUE;
final long[] allCurrentBreaks = pageBox.getPhysicalBreaks(RenderNode.VERTICAL_AXIS);
if (allCurrentBreaks.length == 0)
{
// No maximum height.
throw new InvalidReportStateException("No page given. This is really bad.");
}
// Note: For now, we limit both the header and footer to a single physical
// page. This safes me a lot of trouble for now.
final long lastBreakLocal = allCurrentBreaks[allCurrentBreaks.length - 1];
final long reservedHeight = PaginationStepLib.restrictPageAreaHeights(pageBox, allCurrentBreaks);
if (reservedHeight >= lastBreakLocal)
{
// This is also bad. There will be no space left to print a single element.
throw new InvalidReportStateException("Header and footer consume the whole page. No space left for normal-flow.");
}
PaginationStepLib.configureBreakUtility(basePageBreakList, pageBox, allCurrentBreaks, reservedHeight, lastBreakLocal);
final long pageEnd = basePageBreakList.getLastMasterBreak();
final long pageHeight = pageBox.getPageHeight();
this.paginationTableState = new PaginationTableState
(pageHeight, pageBox.getPageOffset(), pageEnd, basePageBreakList);
// now process all the other content (excluding the header and footer area)
if (startBlockLevelBox(pageBox))
{
processBoxChilds(pageBox);
}
finishBlockLevelBox(pageBox);
PaginationStepLib.assertProgress(pageBox);
final long usedPageHeight = Math.min(pageBox.getHeight(), usablePageHeight);
final long masterBreak = basePageBreakList.getLastMasterBreak();
final boolean overflow;
if (breakIndicatorEncountered != null)
{
if (breakIndicatorEncountered.getY() <= pageBox.getPageOffset())
{
overflow = usedPageHeight > masterBreak;
}
else
{
overflow = true;
}
}
else
{
overflow = usedPageHeight > masterBreak;
}
final boolean nextPageContainsContent = (pageBox.getHeight() > masterBreak);
return new PaginationResult(basePageBreakList, overflow, nextPageContainsContent, visualState);
}
finally
{
this.breakIndicatorEncountered = null;
this.paginationTableState = null;
this.visualState = null;
this.shiftState = null;
getEventWatch().stop();
getSummaryWatch().stop(true);
}
}
protected void processParagraphChilds(final ParagraphRenderBox box)
{
processBoxChilds(box);
}
protected boolean startBlockLevelBox(final RenderBox box)
{
box.setOverflowAreaHeight(box.getCachedHeight());
final boolean retval = handleStartBlockLevelBox(box);
installTableContext(box);
return retval;
}
private boolean handleStartBlockLevelBox(final RenderBox box)
{
this.shiftState = shiftStatePool.create(box, shiftState);
final long shift = shiftState.getShiftForNextChild();
PaginationStepLib.assertBlockPosition(box, shift);
handleBlockLevelBoxFinishedMarker(box, shift);
if (shiftState.isManualBreakSuspended() == false)
{
if (handleManualBreakOnBox(box, shiftState, breakPending))
{
breakPending = false;
return true;
}
breakPending = false;
}
// If this box does not cross any (major or minor) break, it may need no additional shifting at all.
final RenderLength fixedPositionLength = box.getBoxDefinition().getFixedPosition();
if (shiftState.isManualBreakSuspended() ||
RenderLength.AUTO.equals(fixedPositionLength) ||
paginationTableState.isFixedPositionProcessingSuspended())
{
return handleAutomaticPagebreak(box, shiftState);
}
// If you've come this far, this means, that your box has a fixed position defined.
final long boxY = box.getY();
final long shiftedBoxPosition = boxY + shift;
final long fixedPositionResolved = fixedPositionLength.resolve(paginationTableState.getPageHeight(), 0);
final PageBreakPositions breakUtility = paginationTableState.getBreakPositions();
final long fixedPositionInFlow = breakUtility.computeFixedPositionInFlow(shiftedBoxPosition, fixedPositionResolved);
if (fixedPositionInFlow < shiftedBoxPosition)
{
// ... but the fixed position is invalid, so treat it as non-defined.
return handleAutomaticPagebreak(box, shiftState);
}
// The computed break seems to be valid.
// Compute what happens if the whole box can fit on the current page.
// We have an opportunity to optimize our processing by skipping all content if there are no
// manual pagebreaks defined on one of the childs.
if (breakUtility.isCrossingPagebreakWithFixedPosition
(shiftedBoxPosition, box.getHeight(), fixedPositionResolved) == false)
{
return handleFixedPositionWithoutBreakOnBox(box, shift, fixedPositionInFlow);
}
// The box will not fit on the current page.
//
// A box with a fixed position will always be printed at this position, even if it does not seem
// to fit there. If we move the box, we would break the explicit layout constraint 'fixed-position' in
// favour of an implicit one ('page-break: avoid').
// Treat as if there is enough space available. Start the normal processing.
final long fixedPositionDelta = fixedPositionInFlow - shiftedBoxPosition;
shiftState.setShift(shift + fixedPositionDelta);
box.setY(fixedPositionInFlow);
updateStateKey(box);
return true;
}
private boolean handleFixedPositionWithoutBreakOnBox(final RenderBox box,
final long shift,
final long fixedPositionInFlow)
{
final long boxY = box.getY();
final long shiftedBoxPosition = boxY + shift;
final long fixedPositionDelta = fixedPositionInFlow - shiftedBoxPosition;
final RenderBox.BreakIndicator breakIndicator = box.getManualBreakIndicator();
if (breakIndicator == RenderBox.BreakIndicator.INDIRECT_MANUAL_BREAK)
{
// One of the children of this box will cause a manual pagebreak. We have to dive deeper into this child.
// for now, we will only apply the ordinary shift.
box.setY(fixedPositionInFlow);
shiftState.setShift(shift + fixedPositionDelta);
updateStateKey(box);
return true;
}
else // if (breakIndicator == RenderBox.BreakIndicator.NO_MANUAL_BREAK)
{
// The whole box fits on the current page. However, we have to apply the shifting to move the box
// to its defined fixed-position.
//
// As neither this box nor any of the children will cause a pagebreak, we can shift them and skip the processing
// from here.
BoxShifter.shiftBox(box, fixedPositionDelta);
updateStateKeyDeep(box);
return false;
}
}
protected void processBlockLevelNode(final RenderNode node)
{
final long shift = shiftState.getShiftForNextChild();
node.setY(node.getY() + shift);
if (breakPending == false && node.isBreakAfter())
{
breakPending = paginationTableState.isOnPageStart(node.getY()) == false;
if (breakPending)
{
if (logger.isDebugEnabled())
{
logger.debug("BreakPending True for Node:isBreakAfter: " + node);
}
}
}
}
protected void finishBlockLevelBox(final RenderBox box)
{
uninstallTableContext(box);
if (breakPending == false && box.isBreakAfter())
{
breakPending = paginationTableState.isOnPageStart(box.getY() + box.getHeight()) == false;
if (breakPending)
{
if (logger.isDebugEnabled())
{
logger.debug("BreakPending True for Box:isBreakAfter: " + box);
}
}
}
shiftState = shiftState.pop(box.getInstanceId());
}
// At a later point, we have to do some real page-breaking here. We should check, whether the box fits, and should
// shift the box if it doesnt.
protected boolean startCanvasLevelBox(final RenderBox box)
{
box.setOverflowAreaHeight(box.getCachedHeight());
if (box.isCommited())
{
box.setFinishedPaginate(true);
}
installTableContext(box);
shiftState = shiftStatePool.create(box, shiftState);
shiftState.suspendManualBreaks();
box.setY(box.getY() + shiftState.getShiftForNextChild());
return true;
}
protected void finishCanvasLevelBox(final RenderBox box)
{
shiftState = shiftState.pop(box.getInstanceId());
uninstallTableContext(box);
}
protected boolean startRowLevelBox(final RenderBox box)
{
box.setOverflowAreaHeight(box.getCachedHeight());
if (box.isCommited())
{
box.setFinishedPaginate(true);
}
installTableContext(box);
shiftState = shiftStatePool.create(box, shiftState);
shiftState.suspendManualBreaks();
box.setY(box.getY() + shiftState.getShiftForNextChild());
return true;
}
protected void finishRowLevelBox(final RenderBox box)
{
shiftState = shiftState.pop(box.getInstanceId());
uninstallTableContext(box);
}
protected boolean startTableLevelBox(final RenderBox box)
{
box.setOverflowAreaHeight(box.getCachedHeight());
if (box.getNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE_SECTION)
{
final TableSectionRenderBox sectionRenderBox = (TableSectionRenderBox) box;
switch (sectionRenderBox.getDisplayRole())
{
case HEADER:
{
shiftState = shiftStatePool.create(box, shiftState);
paginationTableState = new PaginationTableState(paginationTableState);
paginationTableState.suspendVisualStateCollection(true);
startTableHeaderSection(box, sectionRenderBox);
return false;
}
case FOOTER:
{
shiftState = shiftStatePool.create(box, shiftState);
paginationTableState = new PaginationTableState(paginationTableState);
paginationTableState.suspendVisualStateCollection(true);
// shift the box and all children downwards. Suspend pagebreaks.
final long contextShift = shiftState.getShiftForNextChild();
BoxShifter.shiftBox(box, contextShift);
return false;
}
case BODY:
return startBlockLevelBox(box);
default:
throw new IllegalArgumentException();
}
}
else
{
shiftState = shiftStatePool.create(box, shiftState);
return true;
}
}
private RenderBox findRootBox(RenderBox box)
{
RenderBox parent = box.getParent();
while (parent != null)
{
if (parent.isContainsReservedContent() == false)
{
return box;
}
box = parent;
parent = box.getParent();
}
return box;
}
private void startTableHeaderSection(final RenderBox _box, final TableSectionRenderBox sectionRenderBox)
{
RenderBox box = findRootBox(_box);
final long contextShift = shiftState.getShiftForNextChild();
// shift the header downwards,
// 1. Check that this table actually breaks across the current page. Header position must be
// before the pagebox-offset. If not, return false, after the normal shifting.
final long pageOffset = paginationTableState.getPageOffset();
final long delta = pageOffset - (sectionRenderBox.getY() + contextShift);
if (delta <= 0)
{
BoxShifter.shiftBox(box, contextShift);
if (logger.isDebugEnabled())
{
logger.debug("HEADER NOT SHIFTED; DELTA = " + delta + " -> " + contextShift);
}
sectionRenderBox.setHeaderShift(pageOffsetKey, 0);
return;
}
// 2. Shift the whole header downwards so that its upper edge matches the start of the page.
// return false afterwards.
if (logger.isDebugEnabled())
{
logger.debug("HEADER SHIFTED; DELTA = " + delta + " -> " + contextShift);
}
long headerShift = sectionRenderBox.getHeaderShift(pageOffsetKey);
if (headerShift == 0)
{
final long previousPageOffset =
paginationTableState.getBreakPositions().findPageStartPositionForPageEndPosition(pageOffset);
headerShift = sectionRenderBox.getHeaderShift(previousPageOffset) + box.getHeight();
if (logger.isDebugEnabled())
{
logger.debug("HeaderShift: " + headerShift + " <=> " + pageOffset + " ; prevOffset=" + previousPageOffset);
}
sectionRenderBox.setHeaderShift(pageOffsetKey, headerShift);
}
else
{
if (logger.isDebugEnabled())
{
logger.debug("Existing HeaderShift: " + headerShift + " <=> " + pageOffset);
}
}
if (logger.isDebugEnabled())
{
logger.debug("Table-Height before extension: " + box.getParent().getHeight());
}
BoxShifter.shiftBox(box, delta);
updateStateKeyDeep(box);
shiftState.increaseShift(headerShift);
if (logger.isDebugEnabled())
{
logger.debug("Table-Height after extension: " + box.getParent().getHeight());
}
}
protected void finishTableLevelBox(final RenderBox box)
{
if (box.getNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE_SECTION)
{
final TableSectionRenderBox sectionRenderBox = (TableSectionRenderBox) box;
switch (sectionRenderBox.getDisplayRole())
{
case HEADER:
shiftState = shiftState.pop(box.getInstanceId());
paginationTableState = paginationTableState.pop();
paginationTableState.defineArtificialPageStart(box.getHeight() + paginationTableState.getPageOffset());
break;
case FOOTER:
shiftState = shiftState.pop(box.getInstanceId());
paginationTableState = paginationTableState.pop();
break;
case BODY:
finishBlockLevelBox(box);
break;
default:
throw new IllegalStateException();
}
return;
}
finishBlockLevelBox(box);
}
protected boolean startTableSectionLevelBox(final RenderBox box)
{
box.setOverflowAreaHeight(box.getCachedHeight());
if (box.getNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE_ROW)
{
if (box.isOpen())
{
paginationTableState = new PaginationTableState(paginationTableState);
paginationTableState.suspendVisualStateCollection(false);
}
}
// ignore all other break requests ..
return startBlockLevelBox(box);
}
protected void finishTableSectionLevelBox(final RenderBox box)
{
if (box.getNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE_ROW)
{
if (box.isOpen())
{
paginationTableState = paginationTableState.pop();
}
}
finishBlockLevelBox(box);
}
protected boolean startTableRowLevelBox(final RenderBox box)
{
box.setOverflowAreaHeight(box.getCachedHeight());
return startRowLevelBox(box);
}
protected void finishTableRowLevelBox(final RenderBox box)
{
finishRowLevelBox(box);
}
protected boolean startTableCellLevelBox(final RenderBox box)
{
box.setOverflowAreaHeight(box.getCachedHeight());
installTableContext(box);
return startBlockLevelBox(box);
}
protected void finishTableCellLevelBox(final RenderBox box)
{
finishBlockLevelBox(box);
uninstallTableContext(box);
}
protected boolean startInlineLevelBox(final RenderBox box)
{
box.setOverflowAreaHeight(box.getHeight());
BoxShifter.shiftBox(box, shiftState.getShiftForNextChild());
return false;
}
protected void processInlineLevelNode(final RenderNode node)
{
node.setY(node.getY() + shiftState.getShiftForNextChild());
}
protected void finishInlineLevelBox(final RenderBox box)
{
}
protected boolean startTableColLevelBox(final RenderBox box)
{
return false;
}
protected boolean startTableColGroupLevelBox(final RenderBox box)
{
return false;
}
protected void processCanvasLevelNode(final RenderNode node)
{
node.setY(node.getY() + shiftState.getShiftForNextChild());
}
protected void processRowLevelNode(final RenderNode node)
{
node.setY(node.getY() + shiftState.getShiftForNextChild());
}
protected void processOtherLevelChild(final RenderNode node)
{
node.setY(node.getY() + shiftState.getShiftForNextChild());
}
protected void processTableLevelNode(final RenderNode node)
{
node.setY(node.getY() + shiftState.getShiftForNextChild());
}
protected void processTableRowLevelNode(final RenderNode node)
{
node.setY(node.getY() + shiftState.getShiftForNextChild());
}
protected void processTableSectionLevelNode(final RenderNode node)
{
node.setY(node.getY() + shiftState.getShiftForNextChild());
}
protected void processTableCellLevelNode(final RenderNode node)
{
node.setY(node.getY() + shiftState.getShiftForNextChild());
}
protected void processTableColLevelNode(final RenderNode node)
{
node.setY(node.getY() + shiftState.getShiftForNextChild());
}
private void updateStateKey(final RenderBox box)
{
if (paginationTableState.isVisualStateCollectionSuspended())
{
return;
}
if (box.getNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE)
{
return;
}
final long y = box.getY();
if (y < paginationTableState.getPageEnd())
{
final ReportStateKey stateKey = box.getStateKey();
if (stateKey != null && stateKey.isInlineSubReportState() == false)
{
this.visualState = stateKey;
}
}
}
private void updateStateKeyDeep(final RenderBox box)
{
if (paginationTableState.isVisualStateCollectionSuspended())
{
return;
}
final long y = box.getY();
if (y >= paginationTableState.getPageEnd())
{
return;
}
final ReportStateKey reportStateKey = findOldestProcessKeyStep.find(box);
if (reportStateKey != null && reportStateKey.isInlineSubReportState() == false)
{
this.visualState = reportStateKey;
}
}
private boolean handleAutomaticPagebreak(final RenderBox box,
final PaginationShiftState boxContext)
{
final long shift = boxContext.getShiftForNextChild();
final PageBreakPositions breakUtility = paginationTableState.getBreakPositions();
final long boxHeightAndWidowArea = Math.max
(box.getHeight(), PaginationStepLib.getWidowConstraint(box, boxContext, paginationTableState));
if (breakUtility.isCrossingPagebreak(box.getY(), boxHeightAndWidowArea, shift) == false)
{
// The whole box fits on the current page. No need to do anything fancy.
final RenderBox.BreakIndicator breakIndicator = box.getManualBreakIndicator();
if (breakIndicator == RenderBox.BreakIndicator.INDIRECT_MANUAL_BREAK ||
box.getRestrictFinishedClearOut() == RenderBox.RestrictFinishClearOut.RESTRICTED)
{
// One of the children of this box will cause a manual pagebreak. We have to dive deeper into this child.
// for now, we will only apply the ordinary shift.
final long boxY = box.getY();
box.setY(boxY + shift);
updateStateKey(box);
return true;
}
else // if (breakIndicator == RenderBox.BreakIndicator.NO_MANUAL_BREAK)
{
// As neither this box nor any of the children will cause a pagebreak, we can shift them and skip the processing
// from here.
BoxShifter.shiftBox(box, shift);
updateStateKeyDeep(box);
return false;
}
}
// At this point we know, that the box may cause some shifting. It crosses at least one minor or major pagebreak.
// Right now, we are just evaluating the next break. In a future version, we could search all possible break
// positions up to the next major break.
final long boxY = box.getY();
final long boxYShifted = boxY + shift;
final long nextMinorBreak = breakUtility.findNextBreakPosition(boxYShifted);
final long spaceAvailable = nextMinorBreak - boxYShifted;
// This box sits directly on a pagebreak. This means, the page is empty, and there is no need for additional
// shifting.
if (spaceAvailable == 0)
{
box.setY(boxYShifted);
updateStateKey(box);
if (boxYShifted < nextMinorBreak)
{
// this position is shifted, but not header-shifted
box.markPinned(nextMinorBreak);
}
return true;
}
final long spaceConsumed = PaginationStepLib.computeNonBreakableBoxHeight(box, boxContext, paginationTableState);
if (spaceAvailable < spaceConsumed)
{
// So we have not enough space to fulfill the layout-constraints. Be it so. Lets shift the box to the next
// break.
// check whether we can actually shift the box. We will have to take the previous widow/orphan operations
// into account.
final long nextShift = nextMinorBreak - boxY;
final long shiftDelta = nextShift - shift;
if (shiftDelta > 0)
{
if (logger.isDebugEnabled())
{
logger.debug("Automatic pagebreak, after orphan-opt-out: " + box);
logger.debug("Automatic pagebreak : " + visualState);
}
}
box.setY(boxY + nextShift);
boxContext.setShift(nextShift);
updateStateKey(box);
box.markPinned(nextMinorBreak);
return true;
}
// OK, there *is* enough space available. Start the normal processing
box.setY(boxYShifted);
updateStateKey(box);
return true;
}
private boolean handleManualBreakOnBox(final RenderBox box,
final PaginationShiftState boxContext,
final boolean breakPending)
{
final RenderBox.BreakIndicator breakIndicator = box.getManualBreakIndicator();
// First check the simple cases:
// If the box wants to break, then there's no point in waiting: Shift the box and continue.
if (breakIndicator != RenderBox.BreakIndicator.DIRECT_MANUAL_BREAK && breakPending == false)
{
return false;
}
final long shift = boxContext.getShiftForNextChild();
if (box.getNodeType() == LayoutNodeTypes.TYPE_BOX_BREAKMARK)
{
final BreakMarkerRenderBox bmrb = (BreakMarkerRenderBox) box;
final long pageOffsetForMarker = bmrb.getValidityRange();
final long pageEndForOffset = paginationTableState.getBreakPositions().findPageEndForPageStartPosition(pageOffsetForMarker);
if ((box.getY() + shift) > pageEndForOffset)
{
// we ignore this one. It has been pushed outside of the page for which it was generated.
return false;
}
if (this.breakIndicatorEncountered == null ||
this.breakIndicatorEncountered.getY() < (bmrb.getY() + shift))
{
this.breakIndicatorEncountered = bmrb;
}
}
final PageBreakPositions breakUtility = paginationTableState.getBreakPositions();
final RenderLength fixedPosition = box.getBoxDefinition().getFixedPosition();
final long fixedPositionResolved = fixedPosition.resolve(paginationTableState.getPageHeight(), 0);
final long boxY = box.getY();
final long shiftedBoxY = boxY + shift;
final long nextNonShiftedMajorBreak = breakUtility.findNextMajorBreakPosition(shiftedBoxY);
final long fixedPositionOnNextPage =
breakUtility.computeFixedPositionInFlow(nextNonShiftedMajorBreak, fixedPositionResolved);
final long nextMajorBreak = Math.max(nextNonShiftedMajorBreak, fixedPositionOnNextPage);
if (nextMajorBreak < shiftedBoxY)
{
// This band will be outside the last pagebreak. We can only shift it normally, but there is no way
// that we could shift it to the final position yet.
box.setY(boxY + shift);
}
else
{
final long nextShift = nextMajorBreak - boxY;
final long shiftDelta = nextShift - shift;
box.setY(boxY + nextShift);
boxContext.setShift(nextShift);
}
updateStateKey(box);
final long pageEnd = paginationTableState.getPageEnd();
if (box.getY() < pageEnd)
{
box.markPinned(pageEnd);
}
return true;
}
private void handleBlockLevelBoxFinishedMarker(final RenderBox box, final long shift)
{
if (box.isFinishedPaginate())
{
// if already marked as finished, no need to do more work ..
return;
}
if (box.isCommited())
{
box.setFinishedPaginate(true);
}
else
{
final StaticBoxLayoutProperties sblp = box.getStaticBoxLayoutProperties();
if (sblp.isAvoidPagebreakInside() || sblp.getWidows() > 0 || sblp.getOrphans() > 0)
{
// Check, whether this box sits on a break-position. In that case, we can call that box finished as well.
final long boxY = box.getY();
final PageBreakPositions breakUtility = paginationTableState.getBreakPositions();
final long nextMinorBreak = breakUtility.findNextBreakPosition(boxY + shift);
final long spaceAvailable = nextMinorBreak - (boxY + shift);
// This box sits directly on a pagebreak. No matter how much content we fill in the box, it will not move.
// This makes this box a finished box.
if (spaceAvailable == 0 || box.isPinned())
{
box.setFinishedPaginate(true);
}
}
else
{
// This box defines no constraints that would cause a shift of it later in the process. We can treat it as
// if it is finished already ..
box.setFinishedPaginate(true);
}
}
}
protected void installTableContext(final RenderBox box)
{
if (box.getNodeType() != LayoutNodeTypes.TYPE_BOX_TABLE)
{
return;
}
paginationTableState = new PaginationTableState(paginationTableState);
}
protected void uninstallTableContext(final RenderBox box)
{
if (box.getNodeType() != LayoutNodeTypes.TYPE_BOX_TABLE)
{
return;
}
paginationTableState = paginationTableState.pop();
}
}