/*! * 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.pentaho.reporting.engine.classic.core.layout.model.FinishedRenderNode; 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.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.context.StaticBoxLayoutProperties; import org.pentaho.reporting.engine.classic.core.layout.process.util.OrphanContext; import org.pentaho.reporting.engine.classic.core.layout.process.util.OrphanContextPool; import org.pentaho.reporting.engine.classic.core.layout.process.util.OrphanPassThroughContext; /** * Computes break positions that prevent Orphan and Widow elements, according to the definitions on the boxes * themselves. * <p/> * An Orphan is an element pushed on its own page, with all other elements on the previous pages. This is commonly found * in groups where the group-footer is pushed to the next page. * <p/> * An Widow is an element left on the current page, where all other elements are pushed to the next page. This is * commonly found for group-headers, where the group-body is pushed to the next page. * <p/> * This step calculates the minimum required space that an element would consume if it honours the widow and orphan * rules. * <p/> * When computing the rules, all children are considered, as long as they do not opt-out of the processing. A box that * opts out, has the 'widow-orphan-opt-out' flag set to true. In the simple set of rules, only block-level elements are * considered to opt-in for widow and orphan processing. * <p/> * For orphans, this step computes the minimum space the element requires to be safely placed on this page. If the * elements occupying that space would trigger a manual page-break, the break overrides the orphan rule, and the space * for the orphan processing is limited to the point of the manual break. * <p/> * For widows, this step also computes the minimum space required to satisfy the constraint. Manual breaks override the * widow constraint. During pagination, the pagination processor has to check all parents to see whether their widow * constrains are still fulfilled. * <p/> * If the sum of the widow and orphan constraints is larger than the computed size of the box, the box is considered * unbreakable and behaves as if the "keep-together" flag has been set. * <p/> * The widow-orphan calculation ignores the 'fixed-position' setting when calculating constraints. Combining a * widow-orphan constraint with the fixed-position constrained yields undefined results. The widow and orphan constraint * is only active for paginated reports. It has no effect on flow or streaming report outputs. */ public class OrphanStep extends IterateSimpleStructureProcessStep { private OrphanContext context; private OrphanContextPool contextPool; private OrphanPassThroughContext rootContext; private boolean invalidNodeFound; public OrphanStep() { contextPool = new OrphanContextPool(); rootContext = new OrphanPassThroughContext(); } public boolean processOrphanAnnotation( final LogicalPageBox box ) { invalidNodeFound = false; context = rootContext; startProcessing( box.getContentArea() ); context = null; return invalidNodeFound; } protected void processParagraphChilds( final ParagraphRenderBox box ) { processBoxChilds( box ); } protected boolean startBox( final RenderBox box ) { box.setInvalidWidowOrphanNode( false ); box.setRestrictFinishedClearOut( RenderBox.RestrictFinishClearOut.UNRESTRICTED ); final StaticBoxLayoutProperties properties = box.getStaticBoxLayoutProperties(); if ( properties.isWidowOrphanOptOut() == false ) { context.startChild( box ); } if ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_BREAKMARK ) { context.registerBreakMark( box ); } context = contextPool.create( box, context ); return true; } protected void processOtherNode( final RenderNode node ) { if ( node instanceof FinishedRenderNode ) { final FinishedRenderNode finNode = (FinishedRenderNode) node; if ( finNode.isOrphanLeaf() ) { context.registerFinishedNode( finNode ); // feed information about the collapsed node into the parent to have a consistent pagination run. } } } protected void finishBox( final RenderBox box ) { final OrphanContext oldContext = context; context = oldContext.commit( box ); contextPool.free( oldContext ); if ( box.isInvalidWidowOrphanNode() ) { invalidNodeFound = true; } final StaticBoxLayoutProperties properties = box.getStaticBoxLayoutProperties(); if ( properties.isWidowOrphanOptOut() == false ) { context.endChild( box ); } } }