net.sf.jasperreports.engine.export.JRTextExporter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jasperreports Show documentation
Show all versions of jasperreports Show documentation
Free Java Reporting Library
/*
* JasperReports - Free Java Reporting Library.
* Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
* http://www.jaspersoft.com
*
* Unless you have purchased a commercial license agreement from Jaspersoft,
* the following license terms apply:
*
* This program is part of JasperReports.
*
* JasperReports is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JasperReports 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JasperReports. If not, see .
*/
package net.sf.jasperreports.engine.export;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
import net.sf.jasperreports.engine.DefaultJasperReportsContext;
import net.sf.jasperreports.engine.JRAbstractExporter;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintFrame;
import net.sf.jasperreports.engine.JRPrintPage;
import net.sf.jasperreports.engine.JRPrintText;
import net.sf.jasperreports.engine.JRPropertiesUtil;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.engine.util.JRStyledText;
import net.sf.jasperreports.export.ExportInterruptedException;
import net.sf.jasperreports.export.ExporterInputItem;
import net.sf.jasperreports.export.TextExporterConfiguration;
import net.sf.jasperreports.export.TextReportConfiguration;
import net.sf.jasperreports.export.WriterExporterOutput;
/**
* Exports filled reports in plain text format. The text exporter allows users to define a custom character resolution
* (the number of columns and rows in text format). Since the character resolution is mapped on the actual pixel resolution,
* every character corresponds to a rectangle of pixels. If a certain text element has a smaller size in pixels (width,
* height, or both) than the number of pixels that map to a character, the text element will not be rendered.
*
* The text exporter will yield the better results if the space needed for displaying a text is large. So
* users have to either design reports with few text or export to big text pages. Another good practice is to arrange text
* elements at design time as similar as possible to a grid.
*
* The text exporter tries to convert the JasperReports document into a simple text document with a fixed page width
* and height, measured in characters. Users can specify the desired page width and height, and the engine will make the
* best effort to fit text elements into the corresponding text page. The basic idea of the algorithm is to convert pixels
* to characters (to find a pixel/character ratio). To achieve this, use the following configuration settings
* (see {@link net.sf.jasperreports.export.TextReportConfiguration}):
*
* - {@link net.sf.jasperreports.export.TextReportConfiguration#getCharWidth() getCharWidth()} and
* {@link net.sf.jasperreports.export.TextReportConfiguration#getCharHeight() getCharHeight()} -
* these specify how many pixels in the original report should be mapped onto a character in the exported text.
* - {@link net.sf.jasperreports.export.TextReportConfiguration#getPageWidthInChars() getPageWidthInChars()} and
* {@link net.sf.jasperreports.export.TextReportConfiguration#getPageHeightInChars() getPageHeightInChars()} -
* these specify the text page width and height in characters.
*
*
* Note that both width and height must be specified and that character sizes have priority
* over page sizes.
*
* Since the algorithm causes loss of precision, a few precautions should be taken when
* creating templates that will eventually be exported to plain text:
*
* - Report sizes and text page sizes should be divisible (for example, specify a
* template width of 1,000 pixels and a page width of 100 characters, resulting in a
* character width of 10 pixels).
* - Text element sizes should also follow the preceding rule (for example, if the
* character height is 10 pixels and a particular text element is expected to span two
* rows, then the text element should be 20 pixels tall).
* - For best results, text elements should be aligned in a grid-like fashion.
* - Text fields should not be too small. Following are two examples of problems that
* this can cause:
*
* - If the element height is smaller than the character height, then the element will
* not appear in the exported text file.
* - If the character width is 10 and the element width is 80, then only the first eight
* characters will be displayed.
*
*
*
*
* Users can specify the text that should be inserted between two subsequent pages by using
* the {@link net.sf.jasperreports.export.TextExporterConfiguration#getPageSeparator() getPageSeparator()}
* exporter configuration setting. The default value is two blank lines.
*
* The line separator to be used in the generated text file can be specified using the
* {@link net.sf.jasperreports.export.TextExporterConfiguration#getLineSeparator() getLineSeparator()}
* exporter configuration setting. This is most useful when you want to force a
* particular line separator, knowing that the default line separator is operating system
* dependent, as specified by the line.separator
system property of the JVM.
*
* Check the supplied /demo/samples/text sample to see the kind of output this exporter
* can produce.
*
* @see net.sf.jasperreports.export.TextExporterConfiguration
* @see net.sf.jasperreports.export.TextReportConfiguration
* @author Ionut Nedelcu ([email protected])
*/
public class JRTextExporter extends JRAbstractExporter
{
private static final String TXT_EXPORTER_PROPERTIES_PREFIX = JRPropertiesUtil.PROPERTY_PREFIX + "export.txt.";
public static final String EXCEPTION_MESSAGE_KEY_REQUIRED_POSITIVE_PAGE_OR_CHARACTER_WIDTH = "export.text.required.positive.page.or.character.width";
public static final String EXCEPTION_MESSAGE_KEY_CHARACTER_WIDTH_NEGATIVE = "export.text.character.width.negative";
public static final String EXCEPTION_MESSAGE_KEY_REQUIRED_POSITIVE_PAGE_OR_CHARACTER_HEIGHT = "export.text.required.positive.page.or.character.height";
public static final String EXCEPTION_MESSAGE_KEY_CHARACTER_HEIGHT_NEGATIVE = "export.text.character.height.negative";
protected Writer writer;
char[][] pageData;
protected int pageWidthInChars;
protected int pageHeightInChars;
protected float charWidth;
protected float charHeight;
protected String pageSeparator;
protected String lineSeparator;
protected boolean isTrimLineRight;
protected static final String systemLineSeparator = System.getProperty("line.separator");
protected class ExporterContext extends BaseExporterContext implements JRTextExporterContext
{
}
/**
* @see #JRTextExporter(JasperReportsContext)
*/
public JRTextExporter()
{
this(DefaultJasperReportsContext.getInstance());
}
/**
*
*/
public JRTextExporter(JasperReportsContext jasperReportsContext)
{
super(jasperReportsContext);
exporterContext = new ExporterContext();
}
@Override
protected Class getConfigurationInterface()
{
return TextExporterConfiguration.class;
}
@Override
protected Class getItemConfigurationInterface()
{
return TextReportConfiguration.class;
}
@Override
@SuppressWarnings("deprecation")
protected void ensureOutput()
{
if (exporterOutput == null)
{
exporterOutput =
new net.sf.jasperreports.export.parameters.ParametersWriterExporterOutput(
getJasperReportsContext(),
getParameters(),
getCurrentJasperPrint()
);
}
}
@Override
public void exportReport() throws JRException
{
/* */
ensureJasperReportsContext();
ensureInput();
initExport();
ensureOutput();
writer = getExporterOutput().getWriter();
try
{
exportReportToWriter();
}
catch (IOException e)
{
throw
new JRException(
EXCEPTION_MESSAGE_KEY_OUTPUT_WRITER_ERROR,
new Object[]{jasperPrint.getName()},
e);
}
finally
{
getExporterOutput().close();
}
}
@Override
protected void initExport()
{
super.initExport();
TextExporterConfiguration configuration = getCurrentConfiguration();
lineSeparator = configuration.getLineSeparator();
if (lineSeparator == null)
{
lineSeparator = systemLineSeparator;
}
pageSeparator = configuration.getPageSeparator();
if (pageSeparator == null)
{
pageSeparator = systemLineSeparator + systemLineSeparator;
}
isTrimLineRight = configuration.isTrimLineRight();
}
@Override
protected void initReport()
{
super.initReport();
TextReportConfiguration configuration = getCurrentItemConfiguration();
Float charWidthValue = configuration.getCharWidth();
charWidth = charWidthValue == null ? 0 : charWidthValue;
if (charWidth < 0)
{
throw
new JRRuntimeException(
EXCEPTION_MESSAGE_KEY_CHARACTER_WIDTH_NEGATIVE,
(Object[])null
);
}
else if (charWidth == 0)
{
Integer pageWidthInCharsValue = configuration.getPageWidthInChars();
pageWidthInChars = pageWidthInCharsValue == null ? 0 : pageWidthInCharsValue;
if (pageWidthInChars <= 0)
{
throw
new JRRuntimeException(
EXCEPTION_MESSAGE_KEY_REQUIRED_POSITIVE_PAGE_OR_CHARACTER_WIDTH,
(Object[])null
);
}
charWidth = jasperPrint.getPageWidth() / (float)pageWidthInChars;
}
else
{
pageWidthInChars = (int)(jasperPrint.getPageWidth() / charWidth);
}
Float charHeightValue = configuration.getCharHeight();
charHeight = charHeightValue == null ? 0 : charHeightValue;
if (charHeight < 0)
{
throw
new JRRuntimeException(
EXCEPTION_MESSAGE_KEY_CHARACTER_HEIGHT_NEGATIVE,
(Object[])null
);
}
else if (charHeight == 0)
{
Integer pageHeightInCharsValue = configuration.getPageHeightInChars();
pageHeightInChars = pageHeightInCharsValue == null ? 0 : pageHeightInCharsValue;
if (pageHeightInChars <= 0)
{
throw
new JRRuntimeException(
EXCEPTION_MESSAGE_KEY_REQUIRED_POSITIVE_PAGE_OR_CHARACTER_HEIGHT,
(Object[])null
);
}
charHeight = jasperPrint.getPageHeight() / (float)pageHeightInChars;
}
else
{
pageHeightInChars = (int)(jasperPrint.getPageHeight() / charHeight);
}
}
/**
*
*/
protected void exportReportToWriter() throws JRException, IOException
{
List items = exporterInput.getItems();
for(int reportIndex = 0; reportIndex < items.size(); reportIndex++)
{
ExporterInputItem item = items.get(reportIndex);
setCurrentExporterInputItem(item);
List pages = jasperPrint.getPages();
if (pages != null && pages.size() > 0)
{
PageRange pageRange = getPageRange();
int startPageIndex = (pageRange == null || pageRange.getStartPageIndex() == null) ? 0 : pageRange.getStartPageIndex();
int endPageIndex = (pageRange == null || pageRange.getEndPageIndex() == null) ? (pages.size() - 1) : pageRange.getEndPageIndex();
for(int pageIndex = startPageIndex; pageIndex <= endPageIndex; pageIndex++)
{
if (Thread.interrupted())
{
throw new ExportInterruptedException();
}
JRPrintPage page = pages.get(pageIndex);
/* */
exportPage(page);
if (reportIndex < items.size() - 1 || pageIndex < endPageIndex)
{
writer.write(pageSeparator);
}
}
}
}
writer.flush();
}
/**
* Exports a page to the output writer. Only text elements within the page are considered. For each page, the engine
* creates a matrix of characters and each rendered text element is placed at the appropriate position in the matrix.
* After all texts are parsed, the character matrix is sent to the output writer.
*/
protected void exportPage(JRPrintPage page) throws IOException
{
List elements = page.getElements();
pageData = new char[pageHeightInChars][];
for (int i = 0; i < pageHeightInChars; i++) {
pageData[i] = new char[pageWidthInChars];
Arrays.fill(pageData[i], ' ');
}
exportElements(elements);
for (int i = 0; i < pageHeightInChars; i++)
{
int lineLength = pageWidthInChars;
if (isTrimLineRight)
{
int j = pageWidthInChars - 1;
while (j >= 0 && pageData[i][j] == ' ')
{
j--;
}
lineLength = j + 1;
}
writer.write(pageData[i], 0, lineLength);
writer.write(lineSeparator);
}
JRExportProgressMonitor progressMonitor = getCurrentItemConfiguration().getProgressMonitor();
if (progressMonitor != null)
{
progressMonitor.afterPageExport();
}
}
protected void exportElements(List elements)
{
for (int i = 0; i < elements.size();i++)
{
Object element = elements.get(i);
if (element instanceof JRPrintText)
{
exportText((JRPrintText) element);
}
else if (element instanceof JRPrintFrame)
{
JRPrintFrame frame = (JRPrintFrame) element;
setFrameElementsOffset(frame, false);
try
{
exportElements(frame.getElements());
}
finally
{
restoreElementOffsets();
}
}
}
}
/**
* Renders a text and places it in the output matrix.
*/
protected void exportText(JRPrintText element)
{
int colSpan = getWidthInChars(element.getWidth());
int rowSpan = getHeightInChars(element.getHeight());
int col = getWidthInChars(element.getX() + getOffsetX());
int row = getHeightInChars(element.getY() + getOffsetY());
if (col + colSpan > pageWidthInChars)
{
//if the text exceeds the page width, truncate the column count
colSpan = pageWidthInChars - col;
}
String allText;
JRStyledText styledText = getStyledText(element);
if (styledText == null)
{
allText = "";
}
else
{
allText = styledText.getText();
}
// if the space is too small, the element will not be rendered
if (rowSpan <= 0 || colSpan <= 0)
{
return;
}
if (allText != null && allText.length() == 0)
{
return;
}
// uses an array of string buffers, since the maximum number of rows is already calculated
StringBuilder[] rows = new StringBuilder[rowSpan];
rows[0] = new StringBuilder();
int rowIndex = 0;
int rowPosition = 0;
boolean isFirstLine = true;
// first search for \n, because it causes immediate line break
StringTokenizer lfTokenizer = new StringTokenizer(allText, "\n", true);
label:while (lfTokenizer.hasMoreTokens()) {
String line = lfTokenizer.nextToken();
// if text starts with a new line:
if(isFirstLine && line.equals("\n"))
{
rows[rowIndex].append("");
rowIndex++;
if(rowIndex == rowSpan || !lfTokenizer.hasMoreTokens())
{
break label;
}
rowPosition = 0;
rows[rowIndex] = new StringBuilder();
line = lfTokenizer.nextToken();
}
isFirstLine = false;
// if there is a series of new lines:
int emptyLinesCount = 0;
while(line.equals("\n") && lfTokenizer.hasMoreTokens())
{
emptyLinesCount ++;
line = lfTokenizer.nextToken();
}
if(emptyLinesCount > 1)
{
for(int i = 0; i < emptyLinesCount-1; i++)
{
rows[rowIndex].append("");
rowIndex++;
if(rowIndex == rowSpan)
{
break label;
}
rowPosition = 0;
rows[rowIndex] = new StringBuilder();
//if this is the last empty line:
if(!lfTokenizer.hasMoreTokens() && line.equals("\n"))
{
rows[rowIndex].append("");
break label;
}
}
}
if(!line.equals("\n"))
{
StringTokenizer spaceTokenizer = new StringTokenizer(line, " ", true);
// divide each text line in words
while (spaceTokenizer.hasMoreTokens()) {
String word = spaceTokenizer.nextToken();
// situation: word is larger than the entire column
// in this case breaking occurs in the middle of the word
while (word.length() > colSpan) {
rows[rowIndex].append(word.substring(0, colSpan - rowPosition));
word = word.substring(colSpan - rowPosition, word.length());
rowIndex++;
if(rowIndex == rowSpan)
{
break label;
}
rowPosition = 0;
rows[rowIndex] = new StringBuilder();
}
// situation: word is larger than remaining space on the current line
// in this case, go to the next line
if (rowPosition + word.length() > colSpan)
{
rowIndex++;
if (rowIndex == rowSpan)
{
break label;
}
rowPosition = 0;
rows[rowIndex] = new StringBuilder();
}
// situation: the word is actually a space and it situated at the beginning of a new line
// in this case, it is removed
if (rowIndex > 0 && rowPosition == 0 && word.equals(" "))
{
continue;
}
// situation: the word is small enough to fit in the current line
// in this case just add the word and increment the cursor position
rows[rowIndex].append(word);
rowPosition += word.length();
}
rowIndex++;
if(rowIndex == rowSpan)
{
break;
}
rowPosition = 0;
rows[rowIndex] = new StringBuilder();
}
}
int colOffset = 0;
int rowOffset = 0;
switch (element.getVerticalTextAlign())
{
case BOTTOM :
{
rowOffset = rowSpan - rowIndex;
break;
}
case MIDDLE :
{
rowOffset = (rowSpan - rowIndex) / 2;
break;
}
}
for (int i = 0; i < rowIndex; i++) {
String line = rows[i].toString();
int pos = line.length() - 1;
while (pos >= 0 && line.charAt(pos) == ' ')
{
pos--;
}
line = line.substring(0, pos + 1);
switch (element.getHorizontalTextAlign())
{
case RIGHT :
{
colOffset = colSpan - line.length();
break;
}
case CENTER :
{
colOffset = (colSpan - line.length()) / 2;
break;
}
// if text is justified, there is no offset, but the line text is modified
// the last line in the paragraph is not justified.
case JUSTIFIED :
{
if (i < rowIndex -1)
{
line = justifyText(line, colSpan);
}
break;
}
}
char[] chars = line.toCharArray();
System.arraycopy(chars, 0, pageData[row + rowOffset + i], col + colOffset, chars.length);
}
}
/**
* Justifies the text inside a specified space.
*/
private String justifyText(String s, int width)
{
StringBuilder justified = new StringBuilder();
StringTokenizer t = new StringTokenizer(s, " ");
int tokenCount = t.countTokens();
if (tokenCount <= 1)
{
return s;
}
String[] words = new String[tokenCount];
int i = 0;
while (t.hasMoreTokens())
{
words[i++] = t.nextToken();
}
int emptySpace = width - s.length() + (words.length - 1);
int spaceCount = emptySpace / (words.length - 1);
int remainingSpace = emptySpace % (words.length - 1);
char[] spaces = new char[spaceCount];
Arrays.fill(spaces, ' ');
for (i = 0; i < words.length - 1; i++)
{
justified.append(words[i]);
justified.append(spaces);
if (i < remainingSpace)
{
justified.append(' ');
}
}
justified.append(words[words.length-1]);
return justified.toString();
}
/**
* Transforms height from pixel space to character space.
*/
protected int getHeightInChars(int height)
{
//return (int) (((long) pageHeightInChars * height) / jasperPrint.getPageHeight());
return Math.round(height / charHeight);
}
/**
* Transforms width from pixel space to character space.
*/
protected int getWidthInChars(int width)
{
// return pageWidthInChars * width / jasperPrint.getPageWidth();
return Math.round(width / charWidth);
}
@Override
protected JRStyledText getStyledText(JRPrintText textElement)
{
return styledTextUtil.getStyledText(textElement, noneSelector);
}
@Override
public String getExporterKey()
{
return null;
}
@Override
public String getExporterPropertiesPrefix()
{
return TXT_EXPORTER_PROPERTIES_PREFIX;
}
}