/* * 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) 2006 - 2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.engine.classic.core.layout.process; import org.pentaho.reporting.engine.classic.core.layout.model.AutoRenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.RenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.table.TableCellRenderBox; 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.model.table.rows.TableRowModel; import org.pentaho.reporting.libraries.base.util.GenericObjectTable; public class CleanTableRowsPreparationStep extends IterateStructuralProcessStep { public static class Cell { private int rowIndex; private int colIndex; private int rowSpan; private long y; public Cell( final int rowIndex, final int colIndex, final int rowSpan, final long y ) { this.rowIndex = rowIndex; this.colIndex = colIndex; this.rowSpan = rowSpan; this.y = y; } public int getRowIndex() { return rowIndex; } public int getColIndex() { return colIndex; } public int getRowSpan() { return rowSpan; } public long getY() { return y; } } private GenericObjectTable<Cell> cells; private int requiredAdditionalRows; private int trueRowCount; private TableRowModel rowModel; private int autoBoxIndex; private int firstRowEncountered; private int currentRow; public CleanTableRowsPreparationStep() { } public int process( final TableSectionRenderBox renderBox, final long pageOffset ) { this.firstRowEncountered = -1; this.rowModel = renderBox.getRowModel(); this.trueRowCount = 0; this.cells = new GenericObjectTable<Cell>(); // compute the effective row spans for each row and the sizes it spans startProcessing( renderBox ); return computeSafeCut( pageOffset, cells, trueRowCount ); } public int getFirstRowEncountered() { return firstRowEncountered; } protected boolean startAutoBox( final RenderBox box ) { autoBoxIndex = -1; // treat as transient .. return true; } protected void finishAutoBox( final RenderBox box ) { AutoRenderBox autobox = (AutoRenderBox) box; autobox.setRowIndex( autoBoxIndex ); } protected boolean startTableRowBox( final TableRowRenderBox box ) { int row = box.getRowIndex(); trueRowCount = row + 1; if ( autoBoxIndex == -1 ) { autoBoxIndex = row; } currentRow = row; seenRow( row ); return true; } private void seenRow( int rowNumber ) { if ( firstRowEncountered == -1 ) { firstRowEncountered = rowNumber; } int currentRowMaxRowSpan = rowModel.getMaximumRowSpan( rowNumber ); if ( this.requiredAdditionalRows > 0 ) { // if we have spanned rows pending, reduce the span with each new row started, until every row is consumed. this.requiredAdditionalRows -= 1; } this.requiredAdditionalRows += currentRowMaxRowSpan; this.requiredAdditionalRows -= 1; } protected boolean startTableCellBox( final TableCellRenderBox box ) { int row = currentRow; int col = box.getColumnIndex(); int rowSpan = box.getRowSpan(); Cell c = new Cell( row, col, rowSpan, box.getY() ); for ( int r = 0; r < rowSpan; r += 1 ) { cells.setObject( r + row, col, c ); } return false; } public static int computeSafeCut( final long pageOffset, final GenericObjectTable<Cell> cells, final int trueRowCount ) { int rowForPageOffset = findRowForPageOffset( pageOffset, cells, trueRowCount ); if ( rowForPageOffset == 0 ) { // none of the rows can be cut, the whole table must be preserved. return 0; } if ( rowForPageOffset == trueRowCount ) { // all the table content is before the page-offset, so we can remove all table elements. return trueRowCount; } // algorithm: Start on the right hand side of the table at the rowPagePageOffset. Now move // the cutting point upwards until you reach a start of a cell. Move towards the left until // you hit a spanned cell that is not starting at the current row. Continue moving upwards // and left until you reach the first column of the cell. This marks the safe-cut-off point // for removing cells. int colIdx = cells.getColumnCount() - 1; int rowIdx = rowForPageOffset; while ( colIdx >= 0 ) { Cell c = cells.getObject( rowIdx, colIdx ); if ( c == null ) { // move left when spanned cell area .. colIdx -= 1; continue; } if ( c.getRowIndex() == rowIdx ) { // move left on start of cell colIdx -= 1; continue; } // move upwards on spanned cell. rowIdx -= 1; } return rowIdx; } private static int findRowForPageOffset( final long pageOffset, final GenericObjectTable<Cell> cells, final int trueRowCount ) { int selectedRow = 0; for ( int row = 0; row < trueRowCount; row += 1 ) { long pos = -1; for ( int col = 0; col < cells.getColumnCount(); col += 1 ) { Cell c = cells.getObject( row, col ); if ( c == null ) { continue; } if ( c.getRowIndex() == row ) { pos = c.getY(); } } if ( pos != -1 ) { if ( pageOffset < pos ) { break; } else { selectedRow = row; } } } return selectedRow; } }