/*
* 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;
}
}