/* * 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; } }