/* * 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.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.table.TableSectionRenderBox; import org.pentaho.reporting.engine.classic.core.layout.process.util.BoxShifter; /** * This Step copies all content from the logical page into the page-grid. When done, it clears the content and replaces * the elements with dummy-nodes. These nodes have a fixed-size (the last known layouted size), and will not be * recomputed later. * <p/> * Adjoining dummy-nodes get unified into a single node, thus simplifying and pruning the document tree. * * @author Thomas Morgner */ public final class FillPhysicalPagesStep extends IterateVisualProcessStep { private static class PageContext { private PageContext parent; private long contentEnd; private long contentStart; private PageContext( final long contentStart, final long contentEnd ) { this.contentStart = contentStart; this.contentEnd = contentEnd; } private PageContext( final PageContext parent ) { this.parent = parent; this.contentStart = parent.contentStart; this.contentEnd = parent.contentEnd; } public PageContext pop() { return parent; } public long getContentEnd() { return contentEnd; } public long getContentStart() { return contentStart; } public void increaseContentStartArea( final long value ) { if ( value < 0 ) { throw new NullPointerException(); } contentStart += value; } public boolean isFiltered( final long y, final long height ) { // Special treatment for lines, which have a height of zero. if ( y == contentStart && height == 0 ) { return false; } else if ( ( y + height ) <= contentStart ) { return true; } else if ( y >= contentEnd ) { return true; } else { return false; } } } private PageContext pageContext; private boolean secondPage; public FillPhysicalPagesStep() { } public LogicalPageBox compute( final LogicalPageBox pagebox, final long pageStart, final long pageEnd ) { getEventWatch().start(); getSummaryWatch().start(); try { final long contentStart = pagebox.getHeaderArea().getHeight(); final long contentEnd = ( pageEnd - pageStart ) + contentStart; pageContext = new PageContext( contentStart, contentEnd ); secondPage = pagebox.getPageOffset() != 0; // This is a simple strategy. // Copy and relocate, then prune. (I whished we could prune first, but // this does not work.) // // For the sake of efficiency, we do *not* create private copies for each // physical page. This would be an total overkill. final LogicalPageBox derived = pagebox.derive( true ); // first, shift the normal-flow content downwards. // The start of the logical pagebox might be in the negative range now // The header-size has already been taken into account by the pagination // step. BoxShifter.shiftBoxUnchecked( derived, -pageStart + contentStart ); // now remove all the content that will not be visible at all .. // not processing the header and footer area: they are 'out-of-context' bands processBoxChilds( derived ); // Then add the header at the top - it starts at (0,0) and thus it is // ok to leave it unshifted. // finally, move the footer at the bottom (to the page's bottom, please!) final RenderBox footerArea = derived.getFooterArea(); final RenderBox repeatFooterArea = derived.getRepeatFooterArea(); final long repeatFooterPosition = pagebox.getPageHeight() - repeatFooterArea.getHeight() - footerArea.getHeight(); BoxShifter.shiftBoxUnchecked( repeatFooterArea, repeatFooterPosition ); final long footerPosition = pagebox.getPageHeight() - footerArea.getHeight(); BoxShifter.shiftBoxUnchecked( footerArea, footerPosition ); // the renderer is responsible for painting the page-header and footer .. derived.setPageOffset( 0 ); derived.setPageEnd( contentEnd + footerArea.getHeight() + repeatFooterArea.getHeight() ); return derived; } finally { getEventWatch().stop(); getSummaryWatch().stop( true ); } } protected void processParagraphChilds( final ParagraphRenderBox box ) { processBoxChilds( box ); } protected boolean startBlockLevelBox( final RenderBox box ) { return processBox( box ); } private boolean processBox( final RenderBox box ) { establishPageContext( box ); RenderNode node = box.getFirstChild(); while ( node != null ) { if ( ( node.getNodeType() & LayoutNodeTypes.MASK_BOX ) != LayoutNodeTypes.MASK_BOX && node.isIgnorableForRendering() ) { node = node.getNext(); continue; } if ( node.isContainsReservedContent() ) { node = node.getNext(); continue; } final long y = node.getY(); final long height = node.getOverflowAreaHeight(); if ( node.getNodeType() == LayoutNodeTypes.TYPE_BOX_BREAKMARK || pageContext.isFiltered( y, height ) ) { final RenderNode next = node.getNext(); box.remove( node ); node = next; } else { node = node.getNext(); } } return true; } private void establishPageContext( final RenderBox box ) { if ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE ) { pageContext = new FillPhysicalPagesStep.PageContext( pageContext ); } } protected boolean startCanvasLevelBox( final RenderBox box ) { return processBox( box ); } protected boolean startRowLevelBox( final RenderBox box ) { return processBox( box ); } protected boolean startTableLevelBox( final RenderBox box ) { if ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE_SECTION ) { final TableSectionRenderBox tsr = (TableSectionRenderBox) box; if ( tsr.getDisplayRole() == TableSectionRenderBox.Role.BODY ) { return processBox( box ); } else if ( tsr.getDisplayRole() == TableSectionRenderBox.Role.HEADER ) { // modify the established context .. pageContext.increaseContentStartArea( box.getHeight() ); return false; } else { return false; } } // auto-boxes and sections are accepted as is .. return true; } protected boolean startInlineLevelBox( final RenderBox box ) { return false; } protected boolean startTableSectionLevelBox( final RenderBox box ) { return processBox( box ); } protected boolean startTableRowLevelBox( final RenderBox box ) { return processBox( box ); } protected boolean startTableCellLevelBox( final RenderBox box ) { return processBox( box ); } protected void finishBox( final RenderBox box ) { if ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE ) { pageContext = pageContext.pop(); } } protected void finishBlockLevelBox( final RenderBox box ) { finishBox( box ); } protected void finishCanvasLevelBox( final RenderBox box ) { finishBox( box ); } protected void finishInlineLevelBox( final RenderBox box ) { finishBox( box ); } protected void finishRowLevelBox( final RenderBox box ) { finishBox( box ); } protected void finishTableCellLevelBox( final RenderBox box ) { finishBox( box ); } protected void finishTableColGroupLevelBox( final RenderBox box ) { finishBox( box ); } protected void finishTableColLevelBox( final RenderBox box ) { finishBox( box ); } protected void finishTableLevelBox( final RenderBox box ) { finishBox( box ); } protected void finishTableRowLevelBox( final RenderBox box ) { finishBox( box ); } protected void finishTableSectionLevelBox( final RenderBox box ) { finishBox( box ); } }