/*
* 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.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;
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"));
}
public void close()
{
}
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);
startProcessing(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);
startProcessing(logicalPage);
plainTextPage.writePage();
}
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)
{
// Skip, the node will not be visible.
}
else
{
// 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;
}
}