/*
* 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 java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.text.AttributedCharacterIterator;
import java.util.ArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.model.RenderableComplexText;
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.output.RenderUtility;
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.util.MinorAxisNodeContext;
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.TextWrap;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility;
import org.pentaho.reporting.libraries.base.util.ArgumentNullException;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
public class ComplexTextMinorAxisLayoutStep extends IterateSimpleStructureProcessStep
implements TextMinorAxisLayoutStep
{
private static final Log logger = LogFactory.getLog(ComplexTextMinorAxisLayoutStep.class);
private final boolean strictCompatibility;
private final OutputProcessorMetaData metaData;
private final ResourceManager resourceManager;
private MinorAxisNodeContext nodeContext;
public ComplexTextMinorAxisLayoutStep(final OutputProcessorMetaData metaData,
final ResourceManager resourceManager)
{
this.resourceManager = resourceManager;
ArgumentNullException.validate("metaData", metaData);
this.metaData = metaData;
this.strictCompatibility = getMetaData().isFeatureSupported(OutputProcessorFeature.STRICT_COMPATIBILITY);
}
public void process(final ParagraphRenderBox box, final MinorAxisNodeContext nodeContext, final PageGrid pageGrid)
{
this.nodeContext = nodeContext;
processParagraphChildsComplex(box);
}
public MinorAxisNodeContext getNodeContext()
{
return nodeContext;
}
public OutputProcessorMetaData getMetaData()
{
return metaData;
}
protected void processParagraphChildsComplex(final ParagraphRenderBox box)
{
// Clear the paragraph to throw away previously layouted nodes. This leaves the
// paragraph's pool (where your original text is stored) untouched.
box.clearLayout();
if (box.isComplexParagraph())
{
final RenderBox lineBoxContainer = box.getLineboxContainer();
final StyleSheet layoutContext = box.getStyleSheet();
RenderNode paragraphContainer = lineBoxContainer.getFirstChild();
while (paragraphContainer != null)
{
if (paragraphContainer.getNodeType() != LayoutNodeTypes.TYPE_BOX_LINEBOX)
{
throw new IllegalStateException("Expected ParagraphPoolBox elements.");
}
final ParagraphPoolBox paragraph = (ParagraphPoolBox) paragraphContainer;
addGeneratedComplexTextLines(box, paragraph, layoutContext);
paragraphContainer = paragraphContainer.getNext();
}
}
else
{
final ParagraphPoolBox lineBoxContainer = (ParagraphPoolBox) box.getEffectiveLineboxContainer();
final StyleSheet layoutContext = box.getStyleSheet();
addGeneratedComplexTextLines(box, lineBoxContainer, layoutContext);
}
}
private FontRenderContext createFontRenderContext(final StyleSheet layoutContext)
{
final boolean antiAliasing = RenderUtility.isFontSmooth(layoutContext, getMetaData());
return new FontRenderContext(null, antiAliasing, true);
}
private void addGeneratedComplexTextLines(final ParagraphRenderBox box,
final ParagraphPoolBox lineBoxContainer,
final StyleSheet layoutContext)
{
updateNodeContextWidth(box);
// Determine if anti-aliasing is required or not
if (TextWrap.NONE.equals(box.getStyleSheet().getStyleProperty(TextStyleKeys.TEXT_WRAP)))
{
addCompleteLine(box, lineBoxContainer, layoutContext);
return;
}
// Create a LineBreakMeasurer to break down that string into lines.
final RichTextSpec richText = RichTextSpecProducer.compute(lineBoxContainer, metaData, resourceManager);
final LineBreakIterator lineIterator = createLineBreakIterator(box, layoutContext, richText);
ArrayList<RenderableComplexText> lines = new ArrayList<RenderableComplexText>();
ParagraphFontMetricsImpl metrics = new ParagraphFontMetricsImpl();
while (lineIterator.hasNext())
{
final LineBreakIteratorState state = lineIterator.next();
final RenderableComplexText text = richText.create(lineBoxContainer, state.getStart(), state.getEnd());
text.setTextLayout(state.getTextLayout());
metrics.update(state.getTextLayout());
// and finally add the line to the paragraph
lines.add(text);
}
final double height = metrics.getLineHeight();
for (RenderableComplexText text : lines)
{
final RenderBox line = generateLine(box, lineBoxContainer, text, height, metrics);
box.addGeneratedChild(line);
}
}
private LineBreakIterator createLineBreakIterator(final ParagraphRenderBox box,
final StyleSheet layoutContext,
final RichTextSpec richText)
{
final AttributedCharacterIterator ci = richText.createAttributedCharacterIterator();
final FontRenderContext fontRenderContext = createFontRenderContext(layoutContext);
final boolean breakOnWordBoundary = strictCompatibility ||
layoutContext.getBooleanStyleProperty(TextStyleKeys.WORDBREAK);
if (breakOnWordBoundary)
{
return new WordBreakingLineIterator(box, fontRenderContext, ci, richText.getText());
}
else
{
return new LineBreakIterator(box, fontRenderContext, ci);
}
}
private void addCompleteLine(final ParagraphRenderBox box,
final ParagraphPoolBox lineBoxContainer,
final StyleSheet layoutContext)
{
RichTextSpec richText = RichTextSpecProducer.compute(lineBoxContainer, metaData, resourceManager);
final FontRenderContext fontRenderContext = createFontRenderContext(layoutContext);
final TextLayout textLayout = new TextLayout(richText.createAttributedCharacterIterator(), fontRenderContext);
double height = textLayout.getAscent() + textLayout.getDescent() + textLayout.getLeading();
final RenderableComplexText text = richText.create(lineBoxContainer);
text.setTextLayout(textLayout);
ParagraphFontMetricsImpl metrics = new ParagraphFontMetricsImpl();
metrics.update(textLayout);
final RenderBox line = generateLine(box, lineBoxContainer, text, height, metrics);
// and finally add the line to the paragraph
getNodeContext().updateX2(line.getCachedX2());
box.addGeneratedChild(line);
}
private RenderBox generateLine(final ParagraphRenderBox paragraph,
final ParagraphPoolBox lineBoxContainer,
final RenderableComplexText text,
final double height,
final ParagraphFontMetricsImpl metrics)
{
//derive a new RenderableComplexText object representing the line, that holds on to the TextLayout class.
TextLayout textLayout = text.getTextLayout();
// Store the height and width, so that the other parts of the layouter have access to the information
// text.setCachedHeight();
text.setCachedHeight(Math.max(StrictGeomUtility.toInternalValue(height), lineBoxContainer.getLineHeight()));
text.setCachedWidth(StrictGeomUtility.toInternalValue(textLayout.getAdvance()));
text.setParagraphFontMetrics(metrics);
MinorAxisNodeContext nodeContext = getNodeContext();
final long alignmentX = RenderUtility.computeHorizontalAlignment(paragraph.getTextAlignment(),
nodeContext.getContentAreaWidth(), StrictGeomUtility.toInternalValue(textLayout.getAdvance()));
text.setCachedX(alignmentX + nodeContext.getX());
// Create a shallow copy of the paragraph-pool to act as a line container.
final RenderBox line = (RenderBox) paragraph.getPool().deriveFrozen(false);
line.addGeneratedChild(text);
// Align the line inside the paragraph. (Adjust the cachedX position depending on whether the line is left, centred or right aligned)
line.setCachedX(alignmentX + nodeContext.getX());
line.setCachedWidth(nodeContext.getContentAreaWidth());
return line;
}
private void updateNodeContextWidth(final ParagraphRenderBox paragraph)
{
MinorAxisNodeContext nodeContext = getNodeContext();
final long lineEnd;
final boolean overflowX = paragraph.getStaticBoxLayoutProperties().isOverflowX();
if (overflowX)
{
lineEnd = nodeContext.getX1() + AbstractMinorAxisLayoutStep.OVERFLOW_DUMMY_WIDTH;
}
else
{
lineEnd = nodeContext.getX2();
}
long firstLineIndent = 0; // todo
long lineStart = Math.min(lineEnd, nodeContext.getX1() + firstLineIndent);
if (lineEnd - lineStart <= 0)
{
final long minimumChunkWidth = paragraph.getPool().getMinimumChunkWidth();
nodeContext.updateX2(lineStart + minimumChunkWidth);
logger.warn("Auto-Corrected zero-width first-line on paragraph - " + paragraph.getName());
}
else
{
if (overflowX == false)
{
nodeContext.updateX2(lineEnd);
}
}
}
}