/*
* 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.text;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.ElementAlignment;
import org.pentaho.reporting.engine.classic.core.InvalidReportStateException;
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.PageGrid;
import org.pentaho.reporting.engine.classic.core.layout.model.ParagraphPoolBox;
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.output.OutputProcessorMetaData;
import org.pentaho.reporting.engine.classic.core.layout.process.AbstractMinorAxisLayoutStep;
import org.pentaho.reporting.engine.classic.core.layout.process.IterateSimpleStructureProcessStep;
import org.pentaho.reporting.engine.classic.core.layout.process.alignment.CenterAlignmentProcessor;
import org.pentaho.reporting.engine.classic.core.layout.process.alignment.JustifyAlignmentProcessor;
import org.pentaho.reporting.engine.classic.core.layout.process.alignment.LeftAlignmentProcessor;
import org.pentaho.reporting.engine.classic.core.layout.process.alignment.RightAlignmentProcessor;
import org.pentaho.reporting.engine.classic.core.layout.process.alignment.TextAlignmentProcessor;
import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.EndSequenceElement;
import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.InlineNodeSequenceElement;
import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.ReplacedContentSequenceElement;
import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.SequenceList;
import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.SpacerSequenceElement;
import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.StartSequenceElement;
import org.pentaho.reporting.engine.classic.core.layout.process.layoutrules.TextSequenceElement;
import org.pentaho.reporting.engine.classic.core.layout.process.util.MinorAxisNodeContext;
import org.pentaho.reporting.engine.classic.core.layout.process.util.MinorAxisParagraphBreakState;
import org.pentaho.reporting.engine.classic.core.style.StyleSheet;
import org.pentaho.reporting.engine.classic.core.style.TextStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.WhitespaceCollapse;
import org.pentaho.reporting.libraries.base.util.ArgumentNullException;
public class SimpleTextMinorAxisLayoutStep extends IterateSimpleStructureProcessStep implements TextMinorAxisLayoutStep
{
private static final Log logger = LogFactory.getLog(SimpleTextMinorAxisLayoutStep.class);
private MinorAxisParagraphBreakState lineBreakState;
private MinorAxisNodeContext nodeContext;
private PageGrid pageGrid;
private OutputProcessorMetaData metaData;
private TextAlignmentProcessor centerProcessor;
private TextAlignmentProcessor rightProcessor;
private TextAlignmentProcessor leftProcessor;
private TextAlignmentProcessor justifyProcessor;
public SimpleTextMinorAxisLayoutStep(final OutputProcessorMetaData metaData)
{
ArgumentNullException.validate("metaData", metaData);
this.metaData = metaData;
this.lineBreakState = new MinorAxisParagraphBreakState();
}
public MinorAxisNodeContext getNodeContext()
{
return nodeContext;
}
public void process(final ParagraphRenderBox box,
final MinorAxisNodeContext nodeContext,
final PageGrid pageGrid)
{
this.nodeContext = nodeContext;
this.pageGrid = pageGrid;
try
{
lineBreakState.init(box);
processParagraphChildsNormal(box);
lineBreakState.deinit();
}
finally
{
this.pageGrid = null;
}
}
private void processParagraphChildsNormal(final ParagraphRenderBox box)
{
final MinorAxisParagraphBreakState breakState = getLineBreakState();
if (box.isComplexParagraph())
{
final RenderBox lineboxContainer = box.getLineboxContainer();
RenderNode node = lineboxContainer.getFirstChild();
while (node != null)
{
// all childs of the linebox container must be inline boxes. They
// represent the lines in the paragraph. Any other element here is
// a error that must be reported
if (node.getNodeType() != LayoutNodeTypes.TYPE_BOX_LINEBOX)
{
throw new IllegalStateException("Expected ParagraphPoolBox elements.");
}
final ParagraphPoolBox inlineRenderBox = (ParagraphPoolBox) node;
if (startLine(inlineRenderBox))
{
processBoxChilds(inlineRenderBox);
finishLine(inlineRenderBox, breakState);
}
node = node.getNext();
}
}
else
{
final ParagraphPoolBox node = box.getPool();
if (node.getFirstChild() == null)
{
return;
}
// all childs of the linebox container must be inline boxes. They
// represent the lines in the paragraph. Any other element here is
// a error that must be reported
if (startLine(node))
{
processBoxChilds(node);
finishLine(node, breakState);
}
}
}
private boolean startLine(final RenderBox inlineRenderBox)
{
final MinorAxisParagraphBreakState breakState = getLineBreakState();
if (breakState.isInsideParagraph() == false)
{
return false;
}
if (breakState.isSuspended())
{
return false;
}
breakState.clear();
breakState.add(StartSequenceElement.INSTANCE, inlineRenderBox);
return true;
}
private void finishLine(final RenderBox inlineRenderBox,
final MinorAxisParagraphBreakState breakState)
{
if (breakState.isInsideParagraph() == false || breakState.isSuspended())
{
throw new IllegalStateException("No active breakstate, finish-line cannot continue.");
}
final MinorAxisNodeContext nodeContext = getNodeContext();
final PageGrid pageGrid = getPageGrid();
final OutputProcessorMetaData metaData = getMetaData();
breakState.add(EndSequenceElement.INSTANCE, inlineRenderBox);
final ParagraphRenderBox paragraph = breakState.getParagraph();
final ElementAlignment textAlignment = paragraph.getTextAlignment();
final long textIndent = paragraph.getTextIndent();
final long firstLineIndent = paragraph.getFirstLineIndent();
// This aligns all direct childs. Once that is finished, we have to
// check, whether possibly existing inner-paragraphs are still valid
// or whether moving them violated any of the inner-pagebreak constraints.
final TextAlignmentProcessor processor = create(textAlignment);
final SequenceList sequence = breakState.getSequence();
final long lineEnd;
final boolean overflowX = paragraph.getStaticBoxLayoutProperties().isOverflowX();
if (overflowX)
{
lineEnd = nodeContext.getX1() + AbstractMinorAxisLayoutStep.OVERFLOW_DUMMY_WIDTH;
}
else
{
lineEnd = nodeContext.getX2();
}
long lineStart = Math.min(lineEnd, nodeContext.getX1() + firstLineIndent);
if (lineEnd - lineStart <= 0)
{
final long minimumChunkWidth = paragraph.getPool().getMinimumChunkWidth();
processor.initialize(metaData, sequence, lineStart, lineStart + minimumChunkWidth, pageGrid, overflowX);
nodeContext.updateX2(lineStart + minimumChunkWidth);
logger.warn("Auto-Corrected zero-width first-line on paragraph - " + paragraph.getName());
}
else
{
processor.initialize(metaData, sequence, lineStart, lineEnd, pageGrid, overflowX);
if (overflowX == false)
{
nodeContext.updateX2(lineEnd);
}
}
while (processor.hasNext())
{
final RenderNode linebox = processor.next();
if (linebox.getLayoutNodeType() != LayoutNodeTypes.TYPE_BOX_LINEBOX)
{
throw new IllegalStateException("Line must not be null");
}
paragraph.addGeneratedChild(linebox);
if (processor.hasNext())
{
lineStart = Math.min(lineEnd, nodeContext.getX1() + textIndent);
if (lineEnd - lineStart <= 0)
{
final long minimumChunkWidth = paragraph.getPool().getMinimumChunkWidth();
processor.updateLineSize(lineStart, lineStart + minimumChunkWidth);
nodeContext.updateX2(lineStart + minimumChunkWidth);
logger.warn("Auto-Corrected zero-width text-line on paragraph continuation - " + paragraph.getName());
}
else
{
processor.updateLineSize(lineStart, lineEnd);
if (overflowX == false)
{
nodeContext.updateX2(lineEnd);
}
}
}
}
processor.deinitialize();
}
protected boolean startBox(final RenderBox box)
{
if (lineBreakState.isInsideParagraph() == false)
{
throw new InvalidReportStateException("A inline-level box outside of a paragraph box is not allowed.");
}
final int nodeType = box.getLayoutNodeType();
if (nodeType == LayoutNodeTypes.TYPE_BOX_CONTENT)
{
lineBreakState.add(ReplacedContentSequenceElement.INSTANCE, box);
return false;
}
lineBreakState.add(StartSequenceElement.INSTANCE, box);
return true;
}
protected void processOtherNode(final RenderNode node)
{
if (lineBreakState.isInsideParagraph() == false)
{
throw new InvalidReportStateException("A inline-level box outside of a paragraph box is not allowed.");
}
final int nodeType = node.getNodeType();
if (nodeType == LayoutNodeTypes.TYPE_NODE_FINISHEDNODE)
{
final FinishedRenderNode finNode = (FinishedRenderNode) node;
node.setCachedWidth(finNode.getLayoutedWidth());
return;
}
if (nodeType == LayoutNodeTypes.TYPE_NODE_TEXT)
{
lineBreakState.add(TextSequenceElement.INSTANCE, node);
}
else if (nodeType == LayoutNodeTypes.TYPE_NODE_SPACER)
{
final StyleSheet styleSheet = node.getStyleSheet();
if (WhitespaceCollapse.PRESERVE.equals(styleSheet.getStyleProperty(TextStyleKeys.WHITE_SPACE_COLLAPSE)) &&
styleSheet.getBooleanStyleProperty(TextStyleKeys.TRIM_TEXT_CONTENT) == false)
{
// bug-alert: This condition could indicate a workaround for a logic-flaw in the text-processor
lineBreakState.add(SpacerSequenceElement.INSTANCE, node);
}
else if (lineBreakState.isContainsContent())
{
lineBreakState.add(SpacerSequenceElement.INSTANCE, node);
}
}
else
{
lineBreakState.add(InlineNodeSequenceElement.INSTANCE, node);
}
}
protected void finishBox(final RenderBox box)
{
if (lineBreakState.isInsideParagraph() == false)
{
throw new InvalidReportStateException("A inline-level box outside of a paragraph box is not allowed.");
}
final int nodeType = box.getLayoutNodeType();
if (nodeType == LayoutNodeTypes.TYPE_BOX_CONTENT)
{
return;
}
lineBreakState.add(EndSequenceElement.INSTANCE, box);
}
/**
* Reuse the processors ..
*
* @param alignment
* @return
*/
protected TextAlignmentProcessor create(final ElementAlignment alignment)
{
if (ElementAlignment.CENTER.equals(alignment))
{
if (centerProcessor == null)
{
centerProcessor = new CenterAlignmentProcessor();
}
return centerProcessor;
}
else if (ElementAlignment.RIGHT.equals(alignment))
{
if (rightProcessor == null)
{
rightProcessor = new RightAlignmentProcessor();
}
return rightProcessor;
}
else if (ElementAlignment.JUSTIFY.equals(alignment))
{
if (justifyProcessor == null)
{
justifyProcessor = new JustifyAlignmentProcessor();
}
return justifyProcessor;
}
if (leftProcessor == null)
{
leftProcessor = new LeftAlignmentProcessor();
}
return leftProcessor;
}
public PageGrid getPageGrid()
{
return pageGrid;
}
public OutputProcessorMetaData getMetaData()
{
return metaData;
}
protected MinorAxisParagraphBreakState getLineBreakState()
{
return lineBreakState;
}
}