/*
* 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.pageable.plaintext;
import java.awt.font.TextLayout;
import java.awt.print.Paper;
import java.io.IOException;
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.LogicalPageBox;
import org.pentaho.reporting.engine.classic.core.layout.model.PageGrid;
import org.pentaho.reporting.engine.classic.core.layout.model.ParagraphRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.PhysicalPageBox;
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.RenderableText;
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.LogicalPageKey;
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.PhysicalPageKey;
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.modules.output.pageable.plaintext.driver.PlainTextPage;
import org.pentaho.reporting.engine.classic.core.modules.output.pageable.plaintext.driver.PrinterDriver;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictBounds;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility;
import org.pentaho.reporting.libraries.fonts.encoding.CodePointBuffer;
/**
* Creation-Date: 13.05.2007, 15:49:13
*
* @author Thomas Morgner
*/
public class TextDocumentWriter extends IterateStructuralProcessStep {
private PrinterDriver driver;
private String encoding;
private PlainTextPage plainTextPage;
private long characterWidthInMicroPoint;
private long characterHeightInMicroPoint;
private StrictBounds drawArea;
private RevalidateTextEllipseProcessStep revalidateTextEllipseProcessStep;
private long contentAreaX1;
private long contentAreaX2;
private boolean textLineOverflow;
private CodePointBuffer codePointBuffer;
private boolean ellipseDrawn;
private boolean clipOnWordBoundary;
private boolean watermarkOnTop;
public TextDocumentWriter( final OutputProcessorMetaData metaData, final PrinterDriver driver, final String encoding ) {
if ( encoding == null ) {
throw new NullPointerException();
}
if ( driver == null ) {
throw new NullPointerException();
}
if ( metaData == null ) {
throw new NullPointerException();
}
this.codePointBuffer = new CodePointBuffer( 400 );
this.driver = driver;
this.encoding = encoding;
characterHeightInMicroPoint =
StrictGeomUtility.toInternalValue( metaData.getNumericFeatureValue( TextOutputProcessorMetaData.CHAR_HEIGHT ) );
characterWidthInMicroPoint =
StrictGeomUtility.toInternalValue( metaData.getNumericFeatureValue( TextOutputProcessorMetaData.CHAR_WIDTH ) );
if ( characterHeightInMicroPoint <= 0 || characterWidthInMicroPoint <= 0 ) {
throw new IllegalStateException( "Invalid character box size. Cannot continue." );
}
revalidateTextEllipseProcessStep = new RevalidateTextEllipseProcessStep( metaData );
this.clipOnWordBoundary =
"true".equals( metaData.getConfiguration().getConfigProperty(
"org.pentaho.reporting.engine.classic.core.LastLineBreaksOnWordBoundary" ) );
this.watermarkOnTop = metaData.isFeatureSupported( OutputProcessorFeature.WATERMARK_PRINTED_ON_TOP );
}
@Deprecated
public void close() {
}
@Deprecated
public void open() {
}
public void processPhysicalPage( final PageGrid pageGrid, final LogicalPageBox logicalPage, final int row,
final int col, final PhysicalPageKey pageKey ) throws IOException {
final PhysicalPageBox page = pageGrid.getPage( row, col );
final Paper paper = new Paper();
paper.setSize( StrictGeomUtility.toExternalValue( page.getWidth() ), StrictGeomUtility.toExternalValue( page
.getHeight() ) );
paper.setImageableArea( StrictGeomUtility.toExternalValue( page.getImageableX() ), StrictGeomUtility
.toExternalValue( page.getImageableY() ), StrictGeomUtility.toExternalValue( page.getImageableWidth() ),
StrictGeomUtility.toExternalValue( page.getImageableHeight() ) );
drawArea = new StrictBounds( page.getGlobalX(), page.getGlobalY(), page.getWidth(), page.getHeight() );
plainTextPage = new PlainTextPage( paper, driver, encoding );
processPageBox( logicalPage );
plainTextPage.writePage();
}
public void processLogicalPage( final LogicalPageKey key, final LogicalPageBox logicalPage ) throws IOException {
final Paper paper = new Paper();
paper.setSize( StrictGeomUtility.toExternalValue( logicalPage.getPageWidth() ), StrictGeomUtility
.toExternalValue( logicalPage.getPageHeight() ) );
paper.setImageableArea( 0, 0, StrictGeomUtility.toExternalValue( logicalPage.getPageWidth() ), StrictGeomUtility
.toExternalValue( logicalPage.getPageHeight() ) );
paper.setSize( logicalPage.getPageWidth(), logicalPage.getPageHeight() );
paper.setImageableArea( 0, 0, logicalPage.getPageWidth(), logicalPage.getPageHeight() );
drawArea = new StrictBounds( 0, 0, logicalPage.getWidth(), logicalPage.getHeight() );
plainTextPage = new PlainTextPage( paper, driver, encoding );
processPageBox( logicalPage );
plainTextPage.writePage();
}
protected void processPageBox( LogicalPageBox box ) {
if ( startBlockBox( box ) ) {
if ( !watermarkOnTop ) {
startProcessing( box.getWatermarkArea() );
}
startProcessing( box.getHeaderArea() );
processBoxChilds( box );
startProcessing( box.getRepeatFooterArea() );
startProcessing( box.getFooterArea() );
if ( watermarkOnTop ) {
startProcessing( box.getWatermarkArea() );
}
}
finishBlockBox( box );
}
protected boolean startBlockBox( final BlockRenderBox box ) {
return startBox( box );
}
protected boolean startInlineBox( final InlineRenderBox box ) {
return startBox( box );
}
public boolean startCanvasBox( final CanvasRenderBox box ) {
return startBox( box );
}
protected boolean startRowBox( final RenderBox box ) {
return startBox( box );
}
protected boolean startBox( final RenderBox box ) {
if ( box.getStaticBoxLayoutProperties().isVisible() == false ) {
return false;
}
if ( box.isBoxVisible( drawArea ) == false ) {
return false;
}
return true;
}
protected boolean startTableCellBox( final TableCellRenderBox box ) {
return startBox( box );
}
protected boolean startTableRowBox( final TableRowRenderBox box ) {
return startBox( box );
}
protected boolean startTableSectionBox( final TableSectionRenderBox box ) {
return startBox( box );
}
protected boolean startTableColumnGroupBox( final TableColumnGroupNode box ) {
return startBox( box );
}
protected boolean startTableBox( final TableRenderBox box ) {
return startBox( box );
}
protected boolean startOtherBox( final RenderBox box ) {
return startBox( box );
}
protected boolean startAutoBox( final RenderBox box ) {
return startBox( box );
}
protected void drawText( final RenderableText renderableText ) {
drawText( renderableText, renderableText.getX() + renderableText.getWidth() );
}
protected void drawText( final RenderableText text, final long contentX2 ) {
if ( text.isNodeVisible( drawArea ) == false ) {
return;
}
if ( text.getLength() == 0 ) {
// This text is empty.
return;
}
final GlyphList gs = text.getGlyphs();
final int maxLength = text.computeMaximumTextSize( contentX2 );
final String rawText = gs.getText( text.getOffset(), maxLength, codePointBuffer );
final int x = PlainTextPage.correctedDivisionFloor( ( text.getX() - drawArea.getX() ), characterWidthInMicroPoint );
final int y = PlainTextPage.correctedDivisionFloor( ( text.getY() - drawArea.getY() ), characterHeightInMicroPoint );
int w = Math.min( maxLength, PlainTextPage.correctedDivisionFloor( text.getWidth(), characterWidthInMicroPoint ) );
// filter out results that do not belong to the current physical page
if ( x + w > plainTextPage.getWidth() ) {
w = Math.max( 0, plainTextPage.getWidth() - x );
}
if ( w == 0 ) {
return;
}
if ( y < 0 ) {
return;
}
if ( y >= plainTextPage.getHeight() ) {
return;
}
plainTextPage.addTextChunk( x, y, w, rawText, text.getStyleSheet() );
}
protected void drawComplexText( final RenderNode node ) {
final RenderableComplexText renderableComplexText = (RenderableComplexText) node;
// The text node that is printed will overlap with the ellipse we need to print.
if ( renderableComplexText.isNodeVisible( drawArea ) == false ) {
return;
}
if ( renderableComplexText.getRawText().length() == 0 ) {
// This text is empty.
return;
}
final String text;
TextLayout textLayout = renderableComplexText.getTextLayout();
String debugInfo = textLayout.toString();
String startPos =
debugInfo.substring( debugInfo.indexOf( "[start:" ), debugInfo.indexOf( ", len:" ) ).replace( "[start:", "" );
int startPosIntValue = -1;
try {
startPosIntValue = Integer.parseInt( startPos );
} catch ( NumberFormatException e ) {
// do nothing
}
// workaround for line breaking (since the text cannot be extracted directly from textLayout as stream or String)
// in order to avoid duplicates of same source raw text on multiple lines
if ( ( renderableComplexText.getRawText().length() > textLayout.getCharacterCount() ) && startPosIntValue >= 0 ) {
text =
renderableComplexText.getRawText().substring( startPosIntValue,
textLayout.getCharacterCount() + startPosIntValue );
} else {
text = renderableComplexText.getRawText();
}
final int x =
PlainTextPage.correctedDivisionFloor( ( renderableComplexText.getX() - drawArea.getX() ),
characterWidthInMicroPoint );
final int y =
PlainTextPage.correctedDivisionFloor( ( renderableComplexText.getY() - drawArea.getY() ),
characterHeightInMicroPoint );
int w = text.length();
// filter out results that do not belong to the current physical page
if ( x + w > plainTextPage.getWidth() ) {
w = Math.max( 0, plainTextPage.getWidth() - x );
}
if ( w == 0 ) {
return;
}
if ( y < 0 ) {
return;
}
if ( y >= plainTextPage.getHeight() ) {
return;
}
plainTextPage.addTextChunk( x, y, w, text, renderableComplexText.getStyleSheet() );
}
protected void processOtherNode( final RenderNode node ) {
if ( ( node.getNodeType() == LayoutNodeTypes.TYPE_NODE_TEXT ) == false
&& ( node.getNodeType() == LayoutNodeTypes.TYPE_NODE_COMPLEX_TEXT ) == false ) {
return;
}
if ( isTextLineOverflow() ) {
if ( node.isNodeVisible( drawArea ) == false ) {
return;
}
if ( clipOnWordBoundary == false ) {
if ( node.getNodeType() == 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 ( node.getNodeType() == LayoutNodeTypes.TYPE_NODE_COMPLEX_TEXT ) {
final RenderableComplexText text = (RenderableComplexText) node;
final long x1 = text.getX();
if ( x1 < contentAreaX2 ) {
drawComplexText( node );
}
}
}
if ( node.isVirtualNode() ) {
if ( ellipseDrawn ) {
return;
}
ellipseDrawn = true;
final RenderBox parent = node.getParent();
if ( parent != null ) {
final RenderBox textEllipseBox = parent.getTextEllipseBox();
if ( textEllipseBox != null ) {
processBoxChilds( textEllipseBox );
}
}
return;
}
}
if ( isTextLineOverflow() ) {
if ( node.isNodeVisible( drawArea ) == false ) {
return;
}
final long ellipseSize = extractEllipseSize( node );
final long x1 = node.getX();
final long x2 = x1 + node.getWidth();
final long effectiveAreaX2 = ( contentAreaX2 - ellipseSize );
if ( x2 <= effectiveAreaX2 ) {
// the text will be fully visible.
if ( node.getNodeType() == LayoutNodeTypes.TYPE_NODE_TEXT ) {
drawText( (RenderableText) node );
} else if ( node.getNodeType() == LayoutNodeTypes.TYPE_NODE_COMPLEX_TEXT ) {
drawComplexText( node );
}
} else if ( x1 < contentAreaX2 ) {
// The text node that is printed will overlap with the ellipse we need to print.
if ( node.getNodeType() == LayoutNodeTypes.TYPE_NODE_TEXT ) {
drawText( (RenderableText) node, effectiveAreaX2 );
} else if ( node.getNodeType() == LayoutNodeTypes.TYPE_NODE_COMPLEX_TEXT ) {
drawComplexText( node );
}
final RenderBox parent = node.getParent();
if ( parent != null ) {
final RenderBox textEllipseBox = parent.getTextEllipseBox();
if ( textEllipseBox != null ) {
processBoxChilds( textEllipseBox );
}
}
}
} else {
if ( node.getNodeType() == LayoutNodeTypes.TYPE_NODE_TEXT ) {
drawText( (RenderableText) node );
} else if ( node.getNodeType() == LayoutNodeTypes.TYPE_NODE_COMPLEX_TEXT ) {
drawComplexText( node );
}
}
}
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 processParagraphChilds( final ParagraphRenderBox box ) {
this.contentAreaX1 = box.getContentAreaX1();
this.contentAreaX2 = box.getContentAreaX2();
RenderBox lineBox = (RenderBox) box.getFirstChild();
while ( lineBox != null ) {
processTextLine( lineBox, contentAreaX1, contentAreaX2 );
lineBox = (RenderBox) lineBox.getNext();
}
}
protected void processTextLine( final RenderBox lineBox, final long contentAreaX1, final long contentAreaX2 ) {
final boolean overflowProperty = lineBox.getParent().getStaticBoxLayoutProperties().isOverflowX();
this.textLineOverflow = ( ( lineBox.getX() + lineBox.getWidth() ) > contentAreaX2 ) && overflowProperty == false;
this.ellipseDrawn = false;
if ( textLineOverflow ) {
revalidateTextEllipseProcessStep.compute( lineBox, contentAreaX1, contentAreaX2 );
}
startProcessing( lineBox );
}
public long getContentAreaX2() {
return contentAreaX2;
}
public void setContentAreaX2( final long contentAreaX2 ) {
this.contentAreaX2 = contentAreaX2;
}
public long getContentAreaX1() {
return contentAreaX1;
}
public void setContentAreaX1( final long contentAreaX1 ) {
this.contentAreaX1 = contentAreaX1;
}
public boolean isTextLineOverflow() {
return textLineOverflow;
}
public void setTextLineOverflow( final boolean textLineOverflow ) {
this.textLineOverflow = textLineOverflow;
}
}