/*
* 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.modules.output.table.base;
import org.pentaho.reporting.engine.classic.core.layout.model.BlockRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.CanvasRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.InlineRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.LayoutNodeTypes;
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.model.RenderableReplacedContent;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableReplacedContentBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableText;
import org.pentaho.reporting.engine.classic.core.layout.model.SpacerRenderNode;
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.TableRenderBox;
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.output.OutputProcessorMetaData;
import org.pentaho.reporting.engine.classic.core.layout.process.IterateStructuralProcessStep;
import org.pentaho.reporting.engine.classic.core.layout.process.RevalidateTextEllipseProcessStep;
import org.pentaho.reporting.engine.classic.core.layout.text.GlyphList;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictBounds;
import org.pentaho.reporting.libraries.fonts.encoding.CodePointBuffer;
/**
* Creation-Date: 02.11.2007, 14:14:23
*
* @author Thomas Morgner
*/
public class DefaultTextExtractor extends IterateStructuralProcessStep {
private StringBuffer text;
private Object rawResult;
private RenderNode rawSource;
private StrictBounds paragraphBounds;
private boolean overflowX;
private boolean overflowY;
private boolean textLineOverflow;
private RevalidateTextEllipseProcessStep revalidateTextEllipseProcessStep;
private CodePointBuffer codePointBuffer;
private boolean manualBreak;
// private long contentAreaX1;
private long contentAreaX2;
private boolean ellipseDrawn;
private boolean clipOnWordBoundary;
public DefaultTextExtractor( final OutputProcessorMetaData metaData ) {
if ( metaData == null ) {
throw new NullPointerException();
}
codePointBuffer = new CodePointBuffer( 400 );
text = new StringBuffer( 400 );
paragraphBounds = new StrictBounds();
revalidateTextEllipseProcessStep = new RevalidateTextEllipseProcessStep( metaData );
this.clipOnWordBoundary =
"true".equals( metaData.getConfiguration().getConfigProperty(
"org.pentaho.reporting.engine.classic.core.LastLineBreaksOnWordBoundary" ) );
}
protected CodePointBuffer getCodePointBuffer() {
return codePointBuffer;
}
public Object compute( final RenderBox box ) {
rawResult = null;
rawSource = null;
// initialize it once. It may be overriden later, if there is a real paragraph
paragraphBounds.setRect( box.getX(), box.getY(), box.getWidth(), box.getHeight() );
overflowX = box.isBoxOverflowX();
overflowY = box.isBoxOverflowY();
clearText();
startProcessing( box );
// A simple result. So there's no need to create a rich-text string.
if ( rawResult != null ) {
return rawResult;
}
return text.toString();
}
public String getFormattedtext() {
return text.toString();
}
private long extractEllipseSize( final RenderNode node ) {
if ( node == null ) {
return 0;
}
final RenderBox parent = node.getParent();
if ( parent == null ) {
return 0;
}
final RenderBox textEllipseBox = parent.getTextEllipseBox();
if ( textEllipseBox == null ) {
return 0;
}
return textEllipseBox.getWidth();
}
protected void processOtherNode( final RenderNode node ) {
if ( isTextLineOverflow() ) {
if ( handleOverflow( node ) ) {
return;
}
}
final int nodeType = node.getNodeType();
if ( nodeType == LayoutNodeTypes.TYPE_NODE_TEXT ) {
final RenderableText textNode = (RenderableText) node;
processStandardText( textNode );
if ( textNode.isForceLinebreak() ) {
manualBreak = true;
}
} else if ( nodeType == LayoutNodeTypes.TYPE_NODE_COMPLEX_TEXT ) {
final RenderableComplexText textNode = (RenderableComplexText) node;
if ( processComplexText( textNode ) ) {
return;
}
if ( textNode.isForceLinebreak() ) {
manualBreak = true;
}
} else if ( nodeType == LayoutNodeTypes.TYPE_NODE_SPACER ) {
final SpacerRenderNode spacer = (SpacerRenderNode) node;
final int count = Math.max( 1, spacer.getSpaceCount() );
for ( int i = 0; i < count; i++ ) {
this.text.append( ' ' );
}
}
}
private boolean processComplexText( final RenderableComplexText textNode ) {
if ( isTextLineOverflow() ) {
if ( textNode.isNodeVisible( paragraphBounds, overflowX, overflowY ) == false ) {
return true;
}
final long ellipseSize = extractEllipseSize( textNode );
final long x1 = textNode.getX();
final long x2 = x1 + textNode.getWidth();
final long effectiveAreaX2 = ( contentAreaX2 - ellipseSize );
if ( x2 <= effectiveAreaX2 ) {
// the text will be fully visible.
drawComplexText( textNode );
} else if ( x1 < contentAreaX2 ) {
// The text node that is printed will overlap with the ellipse we need to print.
drawComplexText( textNode );
final RenderBox parent = textNode.getParent();
if ( parent != null ) {
final RenderBox textEllipseBox = parent.getTextEllipseBox();
if ( textEllipseBox != null ) {
processBoxChilds( textEllipseBox );
}
}
}
} else {
drawComplexText( textNode );
}
return false;
}
private void processStandardText( final RenderableText textNode ) {
if ( isTextLineOverflow() ) {
if ( textNode.isNodeVisible( paragraphBounds, overflowX, overflowY ) == false ) {
return;
}
final long ellipseSize = extractEllipseSize( textNode );
final long x1 = textNode.getX();
final long x2 = x1 + textNode.getWidth();
final long effectiveAreaX2 = ( contentAreaX2 - ellipseSize );
if ( x2 <= effectiveAreaX2 ) {
// the text will be fully visible.
drawText( textNode, x2 );
} else if ( x1 < contentAreaX2 ) {
// The text node that is printed will overlap with the ellipse we need to print.
drawText( textNode, effectiveAreaX2 );
final RenderBox parent = textNode.getParent();
if ( parent != null ) {
final RenderBox textEllipseBox = parent.getTextEllipseBox();
if ( textEllipseBox != null ) {
processBoxChilds( textEllipseBox );
}
}
}
} else {
drawText( textNode, textNode.getX() + textNode.getWidth() );
}
}
private boolean handleOverflow( final RenderNode node ) {
if ( node.isNodeVisible( paragraphBounds, overflowX, overflowY ) == false ) {
return true;
}
if ( node.isVirtualNode() ) {
if ( ellipseDrawn ) {
return true;
}
ellipseDrawn = true;
final int nodeType = node.getNodeType();
if ( clipOnWordBoundary == false && nodeType == LayoutNodeTypes.TYPE_NODE_TEXT ) {
final RenderableText text = (RenderableText) node;
final long ellipseSize = extractEllipseSize( node );
final long x1 = text.getX();
final long effectiveAreaX2 = ( contentAreaX2 - ellipseSize );
if ( x1 < contentAreaX2 ) {
// The text node that is printed will overlap with the ellipse we need to print.
drawText( text, effectiveAreaX2 );
}
} else if ( clipOnWordBoundary == false && nodeType == LayoutNodeTypes.TYPE_NODE_COMPLEX_TEXT ) {
final RenderableComplexText text = (RenderableComplexText) node;
final long x1 = text.getX();
if ( x1 < contentAreaX2 ) {
// The text node that is printed will overlap with the ellipse we need to print.
drawComplexText( text );
}
}
final RenderBox parent = node.getParent();
if ( parent != null ) {
final RenderBox textEllipseBox = parent.getTextEllipseBox();
if ( textEllipseBox != null ) {
processBoxChilds( textEllipseBox );
}
}
return true;
}
return false;
}
/**
* Renders the glyphs stored in the text node.
*
* @param renderableText
* the text node that should be rendered.
* @param contentX2
*/
protected void drawText( final RenderableText renderableText, final long contentX2 ) {
if ( renderableText.getLength() == 0 ) {
// This text is empty.
return;
}
final GlyphList gs = renderableText.getGlyphs();
final int maxLength = renderableText.computeMaximumTextSize( contentX2 );
this.text.append( gs.getText( renderableText.getOffset(), maxLength, codePointBuffer ) );
}
protected void drawComplexText( final RenderableComplexText renderableComplexText ) {
String text = renderableComplexText.getRawText();
if ( text.length() == 0 ) {
// This text is empty.
return;
}
this.text.append( text );
}
protected boolean startOtherBox( final RenderBox box ) {
return false;
}
protected boolean isContentField( final RenderBox box ) {
return ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_CONTENT );
}
protected boolean startCanvasBox( final CanvasRenderBox box ) {
return true;
}
protected void processRenderableContent( final RenderableReplacedContentBox box ) {
final RenderableReplacedContent rpc = box.getContent();
this.rawResult = rpc.getRawObject();
this.rawSource = box;
}
protected boolean startBlockBox( final BlockRenderBox box ) {
if ( box.getStaticBoxLayoutProperties().isVisible() == false ) {
return false;
}
return true;
}
protected boolean startRowBox( final RenderBox box ) {
if ( box.getStaticBoxLayoutProperties().isVisible() == false ) {
return false;
}
return true;
}
public RenderNode getRawSource() {
return rawSource;
}
protected boolean startInlineBox( final InlineRenderBox box ) {
if ( box.getStaticBoxLayoutProperties().isVisible() == false ) {
return false;
}
return true;
}
protected boolean startTableCellBox( final TableCellRenderBox box ) {
if ( box.getStaticBoxLayoutProperties().isVisible() == false ) {
return false;
}
return true;
}
protected boolean startTableRowBox( final TableRowRenderBox box ) {
if ( box.getStaticBoxLayoutProperties().isVisible() == false ) {
return false;
}
return true;
}
protected boolean startTableSectionBox( final TableSectionRenderBox box ) {
if ( box.getStaticBoxLayoutProperties().isVisible() == false ) {
return false;
}
return true;
}
protected boolean startTableColumnGroupBox( final TableColumnGroupNode box ) {
if ( box.getStaticBoxLayoutProperties().isVisible() == false ) {
return false;
}
return true;
}
protected boolean startTableBox( final TableRenderBox box ) {
if ( box.getStaticBoxLayoutProperties().isVisible() == false ) {
return false;
}
return true;
}
protected boolean startAutoBox( final RenderBox box ) {
if ( box.getStaticBoxLayoutProperties().isVisible() == false ) {
return false;
}
return true;
}
protected void processParagraphChilds( final ParagraphRenderBox box ) {
rawResult = box.getRawValue();
paragraphBounds.setRect( box.getX(), box.getY(), box.getWidth(), box.getHeight() );
overflowX = box.isBoxOverflowX();
overflowY = box.isBoxOverflowY();
// final long y2 = box.getY() + box.getHeight();
final long contentAreaX1 = box.getContentAreaX1();
contentAreaX2 = box.getContentAreaX2();
RenderBox lineBox = (RenderBox) box.getFirstChild();
while ( lineBox != null ) {
manualBreak = false;
processTextLine( lineBox, contentAreaX1, contentAreaX2 );
if ( manualBreak ) {
addLinebreak();
} else if ( lineBox.getNext() != null ) {
if ( lineBox.getStaticBoxLayoutProperties().isPreserveSpace() == false ) {
addSoftBreak();
} else {
addEmptyBreak();
}
}
lineBox = (RenderBox) lineBox.getNext();
}
}
protected void addEmptyBreak() {
text.append( ' ' );
}
protected void addSoftBreak() {
text.append( ' ' );
}
protected void addLinebreak() {
text.append( '\n' );
}
protected void processTextLine( final RenderBox lineBox, final long contentAreaX1, final long contentAreaX2 ) {
if ( lineBox.isNodeVisible( paragraphBounds, overflowX, overflowY ) == false ) {
return;
}
ellipseDrawn = false;
final boolean overflowProperty = lineBox.getParent().getStaticBoxLayoutProperties().isOverflowX();
this.textLineOverflow = ( ( lineBox.getX() + lineBox.getWidth() ) > contentAreaX2 ) && overflowProperty == false;
if ( textLineOverflow ) {
revalidateTextEllipseProcessStep.compute( lineBox, contentAreaX1, contentAreaX2 );
}
startProcessing( lineBox );
}
public Object getRawResult() {
return rawResult;
}
protected void setRawResult( final Object rawResult ) {
this.rawResult = rawResult;
}
public String getText() {
return text.toString();
}
public int getTextLength() {
return text.length();
}
protected void clearText() {
text.delete( 0, text.length() );
}
protected StrictBounds getParagraphBounds() {
return paragraphBounds;
}
public boolean isTextLineOverflow() {
return textLineOverflow;
}
public boolean isOverflowX() {
return overflowX;
}
public boolean isOverflowY() {
return overflowY;
}
}