/*
* 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)
{
}
}