/* * 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.PerformanceTags; import org.pentaho.reporting.engine.classic.core.function.ProcessingContext; 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.RenderLength; import org.pentaho.reporting.engine.classic.core.layout.model.RenderNode; import org.pentaho.reporting.engine.classic.core.layout.model.table.TableCellRenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.table.TableColumnGroupNode; import org.pentaho.reporting.engine.classic.core.layout.model.table.TableColumnNode; import org.pentaho.reporting.engine.classic.core.layout.model.table.TableRenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.table.columns.TableColumnModel; import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorFeature; import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorMetaData; import org.pentaho.reporting.engine.classic.core.layout.process.text.ComplexTextMinorAxisLayoutStep; import org.pentaho.reporting.engine.classic.core.layout.process.text.SimpleTextMinorAxisLayoutStep; import org.pentaho.reporting.engine.classic.core.layout.process.text.TextMinorAxisLayoutStep; import org.pentaho.reporting.engine.classic.core.layout.process.util.MinorAxisNodeContext; import org.pentaho.reporting.engine.classic.core.layout.process.util.MinorAxisNodeContextPool; import org.pentaho.reporting.engine.classic.core.layout.process.util.MinorAxisTableContext; import org.pentaho.reporting.engine.classic.core.states.PerformanceMonitorContext; import org.pentaho.reporting.libraries.base.util.PerformanceLoggingStopWatch; /** * This process-step computes the effective layout, but it does not take horizontal pagebreaks into account. (It has to * deal with vertical breaks, as they affect the text layout.) * <p/> * This processing step does not ajust anything on the vertical axis. Vertical alignment is handled in a second step. * <p/> * Please note: This layout model (unlike the default CSS model) uses the BOX-WIDTH as computed with. This means, the * defined width specifies the sum of all borders, paddings and the content area width. * * @author Thomas Morgner * @noinspection PointlessArithmeticExpression */ public final class CanvasMinorAxisLayoutStep extends AbstractMinorAxisLayoutStep { private static final Log logger = LogFactory.getLog( CanvasMinorAxisLayoutStep.class ); private MinorAxisNodeContext nodeContext; private MinorAxisNodeContextPool nodeContextPool; private PerformanceLoggingStopWatch paragraphLayoutWatch; private TextMinorAxisLayoutStep textMinorAxisLayoutStep; public CanvasMinorAxisLayoutStep() { nodeContextPool = new MinorAxisNodeContextPool(); } public void initializePerformanceMonitoring( final PerformanceMonitorContext monitorContext ) { super.initializePerformanceMonitoring( monitorContext ); paragraphLayoutWatch = monitorContext.createStopWatch( PerformanceTags.getSummaryTag( PerformanceTags.REPORT_LAYOUT_PROCESS_SUFFIX, getClass().getSimpleName() + ".ParagraphLayout" ) ); } public void close() { super.close(); paragraphLayoutWatch.close(); } public void initialize( final OutputProcessorMetaData metaData, final ProcessingContext processingContext ) { super.initialize( metaData ); if ( metaData.isFeatureSupported( OutputProcessorFeature.COMPLEX_TEXT ) ) { textMinorAxisLayoutStep = new ComplexTextMinorAxisLayoutStep( metaData, processingContext.getResourceManager() ); } else { textMinorAxisLayoutStep = new SimpleTextMinorAxisLayoutStep( metaData ); } } public void compute( final LogicalPageBox root ) { super.compute( root ); } protected boolean startParagraphBox( final RenderBox box ) { if ( box.getNodeType() != LayoutNodeTypes.TYPE_BOX_PARAGRAPH ) { return true; } final ParagraphRenderBox paragraphBox = (ParagraphRenderBox) box; if ( paragraphBox.isLineBoxUnchanged() ) { nodeContext.updateX2( paragraphBox.getCachedMaxChildX2() ); return false; } paragraphLayoutWatch.start(); try { paragraphBox.clearLayout(); textMinorAxisLayoutStep.process( paragraphBox, getNodeContext(), getPageGrid() ); paragraphBox.updateMinorLayoutAge(); paragraphBox.setCachedMaxChildX2( nodeContext.getMaxChildX2() ); } finally { paragraphLayoutWatch.stop( true ); } return false; } protected void processParagraphChilds( final ParagraphRenderBox box ) { throw new IllegalStateException( "Encountered a paragraph where no paragraph was expected." ); } protected MinorAxisNodeContext getNodeContext() { return nodeContext; } protected boolean startBlockLevelBox( final RenderBox box ) { nodeContext = nodeContextPool.createContext( box, nodeContext, true ); if ( checkCacheValid( box ) ) { return false; } startTableContext( box ); final long x = nodeContext.getParentX1(); final long left = box.getInsetsLeft(); final long right = box.getInsetsRight(); final long width = MinorAxisLayoutStepUtil.resolveNodeWidthOnStart( box, nodeContext, x ); assert width >= 0; nodeContext.setArea( x, left, right, width ); if ( startParagraphBox( box ) == false ) { return false; } return true; } protected void processBlockLevelNode( final RenderNode node ) { assert ( node instanceof FinishedRenderNode ); node.setCachedX( nodeContext.getX1() ); node.setCachedWidth( nodeContext.getContentAreaWidth() ); } protected void finishBlockLevelBox( final RenderBox box ) { try { if ( checkCacheValid( box ) ) { if ( box.isVisible() ) { nodeContext.updateParentX2( box.getCachedX2() ); } return; } box.setCachedX( nodeContext.getX() ); box.setContentAreaX1( nodeContext.getX1() ); box.setContentAreaX2( nodeContext.getX2() ); if ( finishTableContext( box ) == false ) { box.setCachedWidth( MinorAxisLayoutStepUtil.resolveNodeWidthOnFinish( box, nodeContext, isStrictLegacyMode() ) ); } if ( box.isVisible() ) { nodeContext.updateParentX2( box.getCachedX2() ); } } finally { nodeContext = nodeContext.pop(); } } private long computeCanvasPosition( final RenderNode node ) { final long contentAreaX1 = nodeContext.getParentX1(); final long bcw = nodeContext.getBlockContextWidth(); final double posX = node.getNodeLayoutProperties().getPosX(); final long position = RenderLength.resolveLength( bcw, posX ); return ( contentAreaX1 + position ); } protected boolean startCanvasLevelBox( final RenderBox box ) { nodeContext = nodeContextPool.createContext( box, nodeContext, false ); if ( checkCacheValid( box ) ) { return false; } startTableContext( box ); final long x = computeCanvasPosition( box ); final long left = box.getInsetsLeft(); final long right = box.getInsetsRight(); final long width; if ( isStrictLegacyMode() && box.useMinimumChunkWidth() == false ) { width = MinorAxisLayoutStepUtil.resolveNodeWidthOnStartForCanvasLegacy( box, nodeContext, x ); } else { width = MinorAxisLayoutStepUtil.resolveNodeWidthOnStart( box, nodeContext, x ); } assert width >= 0; nodeContext.setArea( x, left, right, width ); if ( startParagraphBox( box ) == false ) { return false; } return true; } protected void processCanvasLevelNode( final RenderNode node ) { assert ( node instanceof FinishedRenderNode ); node.setCachedX( computeCanvasPosition( node ) ); node.setCachedWidth( node.getMaximumBoxWidth() ); if ( node.isVisible() ) { nodeContext.updateParentX2( node.getCachedX2() ); } } protected void finishCanvasLevelBox( final RenderBox box ) { try { if ( checkCacheValid( box ) ) { if ( box.isVisible() ) { nodeContext.updateParentX2( box.getCachedX2() ); } return; } // make sure that the width takes all the borders and paddings into account. box.setCachedX( nodeContext.getX() ); box.setContentAreaX1( nodeContext.getX1() ); box.setContentAreaX2( nodeContext.getX2() ); if ( finishTableContext( box ) == false ) { box.setCachedWidth( MinorAxisLayoutStepUtil.resolveNodeWidthOnFinish( box, nodeContext, isStrictLegacyMode() ) ); } if ( box.isVisible() ) { nodeContext.updateParentX2( box.getCachedX2() ); } } finally { nodeContext = nodeContext.pop(); } } private long computeRowPosition( final RenderNode node ) { // The y-position of a box depends on the parent. final RenderBox parent = node.getParent(); // A table row is something special. Although it is a block box, // it layouts its children from left to right if ( parent == null ) { // there's no parent .. return 0; } final RenderNode prev = node.getPrev(); if ( prev != null ) { if ( prev.isVisible() ) { // we have a sibling. Position yourself directly to the right of your sibling .. return prev.getCachedX() + prev.getCachedWidth(); } else { // we have a sibling. Position yourself directly to the right of your sibling .. return prev.getCachedX(); } } else { return nodeContext.getParentX1(); } } protected boolean startRowLevelBox( final RenderBox box ) { nodeContext = nodeContextPool.createContext( box, nodeContext, false ); if ( checkCacheValid( box ) ) { return false; } startTableContext( box ); final long x = computeRowPosition( box ); final long left = box.getInsetsLeft(); final long right = box.getInsetsRight(); final long width = MinorAxisLayoutStepUtil.resolveNodeWidthOnStart( box, nodeContext, x ); assert width >= 0; nodeContext.setArea( x, left, right, width ); if ( startParagraphBox( box ) == false ) { return false; } return true; } protected void processRowLevelNode( final RenderNode node ) { assert ( node instanceof FinishedRenderNode ); node.setCachedX( computeRowPosition( node ) ); node.setCachedWidth( node.getMaximumBoxWidth() ); if ( node.isVisible() ) { nodeContext.updateParentX2( node.getCachedX2() ); } } protected void finishRowLevelBox( final RenderBox box ) { try { if ( checkCacheValid( box ) ) { if ( box.isVisible() ) { nodeContext.updateParentX2( box.getCachedX2() ); } return; } box.setCachedX( nodeContext.getX() ); box.setContentAreaX1( nodeContext.getX1() ); box.setContentAreaX2( nodeContext.getX2() ); if ( finishTableContext( box ) == false ) { final long cachedWidth = MinorAxisLayoutStepUtil.resolveNodeWidthOnFinish( box, nodeContext, isStrictLegacyMode() ); box.setCachedWidth( cachedWidth ); } if ( box.isVisible() ) { nodeContext.updateParentX2( box.getCachedX2() ); } } finally { nodeContext = nodeContext.pop(); } } protected boolean startInlineLevelBox( final RenderBox box ) { throw new InvalidReportStateException( "A inline-level box outside of a paragraph box is not allowed." ); } protected void processInlineLevelNode( final RenderNode node ) { throw new InvalidReportStateException( "A inline-level box outside of a paragraph box is not allowed." ); } protected void finishInlineLevelBox( final RenderBox box ) { throw new InvalidReportStateException( "A inline-level box outside of a paragraph box is not allowed." ); } // Table-sections or auto-boxes masking as tables (treated as table-sections nonetheless). protected boolean startTableLevelBox( final RenderBox box ) { nodeContext = nodeContextPool.createContext( box, nodeContext, true ); if ( checkCacheValid( box ) ) { return false; } if ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE_COL_GROUP ) { startTableColGroup( (TableColumnGroupNode) box ); } else if ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE_COL ) { startTableCol( (TableColumnNode) box ); } else { startTableSectionOrRow( box ); } return true; } private void startTableSectionOrRow( final RenderBox box ) { final MinorAxisTableContext tableContext = getTableContext(); final long x = nodeContext.getParentX1(); final long width; if ( tableContext.isStructureValidated() ) { width = tableContext.getTable().getColumnModel().getCachedSize(); } else { width = MinorAxisLayoutStepUtil.resolveNodeWidthOnStart( box, nodeContext, x ); } nodeContext.setArea( x, 0, 0, width ); } protected void processTableLevelNode( final RenderNode node ) { assert ( node instanceof FinishedRenderNode ); node.setCachedX( nodeContext.getX1() ); node.setCachedWidth( nodeContext.getContentAreaWidth() ); } protected void finishTableLevelBox( final RenderBox box ) { try { if ( checkCacheValid( box ) ) { if ( box.isVisible() ) { nodeContext.updateParentX2( box.getCachedX2() ); } return; } if ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE_COL_GROUP ) { finishTableColGroup( (TableColumnGroupNode) box ); } else if ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE_COL ) { finishTableCol( (TableColumnNode) box ); } else { box.setCachedX( nodeContext.getX() ); box.setContentAreaX1( nodeContext.getX1() ); box.setContentAreaX2( nodeContext.getX2() ); box.setCachedWidth( resolveTableWidthOnFinish( box ) ); if ( box.isVisible() ) { nodeContext.updateParentX2( box.getCachedX2() ); } } } finally { nodeContext = nodeContext.pop(); } } protected boolean startTableSectionLevelBox( final RenderBox box ) { nodeContext = nodeContextPool.createContext( box, nodeContext, true ); startTableSectionOrRow( box ); return true; } protected void processTableSectionLevelNode( final RenderNode node ) { assert ( node instanceof FinishedRenderNode ); node.setCachedX( nodeContext.getX1() ); node.setCachedWidth( nodeContext.getContentAreaWidth() ); } protected void finishTableSectionLevelBox( final RenderBox box ) { try { box.setCachedX( nodeContext.getX() ); box.setContentAreaX1( nodeContext.getX1() ); box.setContentAreaX2( nodeContext.getX2() ); box.setCachedWidth( resolveTableWidthOnFinish( box ) ); if ( box.isVisible() ) { nodeContext.updateParentX2( box.getCachedX2() ); } } finally { nodeContext = nodeContext.pop(); } } private long resolveTableWidthOnFinish( final RenderBox box ) { final MinorAxisTableContext tableContext = getTableContext(); if ( tableContext.isStructureValidated() ) { return tableContext.getTable().getColumnModel().getCachedSize(); } else { return MinorAxisLayoutStepUtil.resolveNodeWidthOnFinish( box, nodeContext, isStrictLegacyMode() ); } } protected boolean startTableRowLevelBox( final RenderBox box ) { nodeContext = nodeContextPool.createContext( box, nodeContext, false ); if ( box.getNodeType() != LayoutNodeTypes.TYPE_BOX_TABLE_CELL ) { startTableSectionOrRow( box ); return true; } final MinorAxisTableContext tableContext = getTableContext(); final TableCellRenderBox tableCellRenderBox = (TableCellRenderBox) box; // This is slightly different for table cells ... final int columnIndex = tableCellRenderBox.getColumnIndex(); final TableColumnModel columnModel = tableContext.getColumnModel(); // cell-size does not include border spacing final long startOfRowX = nodeContext.getParentX1(); final long x = startOfRowX + columnModel.getCellPosition( columnIndex ); final long insetsLeft = Math.max( box.getInsetsLeft(), columnModel.getBorderSpacing() / 2 ); final long insetsRight = Math.max( box.getInsetsRight(), columnModel.getBorderSpacing() / 2 ); final long width = computeCellWidth( tableCellRenderBox ); nodeContext.setArea( x, insetsLeft, insetsRight, width ); return true; } protected void processTableRowLevelNode( final RenderNode node ) { assert ( node instanceof FinishedRenderNode ); node.setCachedX( nodeContext.getX1() ); node.setCachedWidth( nodeContext.getContentAreaWidth() ); } protected void finishTableRowLevelBox( final RenderBox box ) { try { box.setCachedX( nodeContext.getX() ); box.setContentAreaX1( nodeContext.getX1() ); box.setContentAreaX2( nodeContext.getX2() ); if ( box.getNodeType() != LayoutNodeTypes.TYPE_BOX_TABLE_CELL ) { // break-marker boxes etc. box.setCachedWidth( resolveTableWidthOnFinish( box ) ); if ( box.isVisible() ) { nodeContext.updateParentX2( box.getCachedX2() ); } } else { box.setCachedWidth( MinorAxisLayoutStepUtil.resolveNodeWidthOnFinish( box, nodeContext, isStrictLegacyMode() ) ); final TableCellRenderBox cell = (TableCellRenderBox) box; final MinorAxisTableContext tableContext = getTableContext(); final TableRenderBox table = tableContext.getTable(); if ( tableContext.isStructureValidated() == false ) { table.getColumnModel().updateCellSize( cell.getColumnIndex(), cell.getColSpan(), box.getCachedWidth() - box.getInsets() ); } if ( box.isVisible() ) { nodeContext.updateParentX2( box.getCachedX2() ); } } } finally { nodeContext = nodeContext.pop(); } } protected boolean startTableCellLevelBox( final RenderBox box ) { return startBlockLevelBox( box ); } protected void processTableCellLevelNode( final RenderNode node ) { processBlockLevelNode( node ); } protected void finishTableCellLevelBox( final RenderBox box ) { finishBlockLevelBox( box ); } protected boolean startTableColGroupLevelBox( final RenderBox box ) { nodeContext = nodeContextPool.createContext( box, nodeContext, false ); if ( checkCacheValid( box ) ) { return false; } if ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE_COL ) { startTableCol( (TableColumnNode) box ); } return true; } protected void finishTableColGroupLevelBox( final RenderBox box ) { try { if ( checkCacheValid( box ) ) { return; } if ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE_COL ) { finishTableCol( (TableColumnNode) box ); } } finally { nodeContext = nodeContext.pop(); } } private void startTableCol( final TableColumnNode box ) { } private void finishTableCol( final TableColumnNode box ) { } private void startTableColGroup( final TableColumnGroupNode box ) { } private void finishTableColGroup( final TableColumnGroupNode box ) { } }