Ptidej-v5.2-master/Ptidej Reporting Tests/src/net/sf/jasperreports/engine/export/JRTextExporter.java
/*
 * ============================================================================
 * GNU Lesser General Public License
 * ============================================================================
 *
 * JasperReports - Free Java report-generating library.
 * Copyright (C) 2001-2006 JasperSoft Corporation http://www.jaspersoft.com
 * 
 * This library 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 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
 * 
 * JasperSoft Corporation
 * 303 Second Street, Suite 450 North
 * San Francisco, CA 94107
 * http://www.jaspersoft.com
 */
package net.sf.jasperreports.engine.export;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;

import net.sf.jasperreports.engine.JRAbstractExporter;
import net.sf.jasperreports.engine.JRAlignment;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JRPrintFrame;
import net.sf.jasperreports.engine.JRPrintPage;
import net.sf.jasperreports.engine.JRPrintText;
import net.sf.jasperreports.engine.JRStyledTextAttributeSelector;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.util.JRProperties;
import net.sf.jasperreports.engine.util.JRStyledText;

/**
 * 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. Because of
 * this, users must take some precautions when creating reports for text export. First, they must make sure the page size in
 * characters is large enough to render the report, because if the report pages contain too much text, some of it may be
 * rendered only partially. On the other hand, if the character resolution is too small compared to the pixel resolution
 * (say, a height of 20 characters for a page 800 pixels tall) all texts with sizes smaller than the one needed to map to a
 * character, will not be displayed (in the previous examplle, a text element needs to be at least 800/20 = 40 pixels tall
 * in order to be rendered).
 * <p>
 * As a conclusion, 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.
 *
 * @see JRExporterParameter
 * @author Ionut Nedelcu (ionutned@users.sourceforge.net)
 * @version $Id: JRTextExporter.java,v 1.1 2008/09/29 16:20:46 guehene Exp $
 */
public class JRTextExporter extends JRAbstractExporter
{
	private static final String TXT_EXPORTER_PROPERTIES_PREFIX = JRProperties.PROPERTY_PREFIX + "export.txt.";

	protected int pageWidth;
	protected int pageHeight;
	protected int characterHeight;
	protected int characterWidth;
	protected JRExportProgressMonitor progressMonitor;
	protected Writer writer;
	char[][] pageData;
	protected String betweenPagesText;
	protected String lineSeparator;

	protected static final String systemLineSeparator = System.getProperty("line.separator");

	/**
	 *
	 */
	public void exportReport() throws JRException
	{
		this.progressMonitor = (JRExportProgressMonitor)this.parameters.get(JRExporterParameter.PROGRESS_MONITOR);

		/*   */
		setOffset();

		/*   */
		setInput();

		if (!this.parameters.containsKey(JRExporterParameter.FILTER))
		{
			this.filter = createFilter(TXT_EXPORTER_PROPERTIES_PREFIX);
		}

		/*   */
		if (!this.isModeBatch)
		{
			setPageRange();
		}

		String encoding = 
			getStringParameterOrDefault(
				JRExporterParameter.CHARACTER_ENCODING, 
				JRExporterParameter.PROPERTY_CHARACTER_ENCODING
				);

		Integer characterWidthParam = (Integer) this.parameters.get(JRTextExporterParameter.CHARACTER_WIDTH);
		if (characterWidthParam != null) {
			this.characterWidth = characterWidthParam.intValue();
			if (this.characterWidth < 0)
				throw new JRException("Character width must be greater than 0");
		}
		else {
			Integer pageWidthParam = (Integer) this.parameters.get(JRTextExporterParameter.PAGE_WIDTH);
			if (pageWidthParam != null) {
				this.pageWidth = pageWidthParam.intValue();
				if (this.pageWidth <= 0)
					throw new JRException("Page width must be greater than 0");
			} else {
				throw new JRException("Character or page width must be specified");
			}
		}


		Integer characterHeightParam = (Integer) this.parameters.get(JRTextExporterParameter.CHARACTER_HEIGHT);
		if (characterHeightParam != null) {
			this.characterHeight = characterHeightParam.intValue();
			if (this.characterHeight < 0)
				throw new JRException("Character height must be greater than 0");
		}
		else {
			Integer pageHeightParam = (Integer) this.parameters.get(JRTextExporterParameter.PAGE_HEIGHT);
			if (pageHeightParam != null) {
				this.pageHeight = pageHeightParam.intValue();
				if (this.pageHeight <= 0)
					throw new JRException("Page height must be greater than 0");
			}
			else {
				throw new JRException("Character or page height must be specified");
			}
		}


		this.betweenPagesText = (String) this.parameters.get(JRTextExporterParameter.BETWEEN_PAGES_TEXT);
		if (this.betweenPagesText == null) {
			this.betweenPagesText = systemLineSeparator + systemLineSeparator;
		}

		this.lineSeparator = (String) this.parameters.get(JRTextExporterParameter.LINE_SEPARATOR);
		if (this.lineSeparator == null) {
			this.lineSeparator = systemLineSeparator;
		}

		StringBuffer sb = (StringBuffer)this.parameters.get(JRExporterParameter.OUTPUT_STRING_BUFFER);
		if (sb != null)
		{
			try
			{
				this.writer = new StringWriter();
				exportReportToWriter();
				sb.append(this.writer.toString());
			}
			catch (IOException e)
			{
				throw new JRException("Error writing to StringBuffer writer : " + this.jasperPrint.getName(), e);
			}
			finally
			{
				if (this.writer != null)
				{
					try
					{
						this.writer.close();
					}
					catch(IOException e)
					{
					}
				}
			}
		}
		else
		{
			this.writer = (Writer)this.parameters.get(JRExporterParameter.OUTPUT_WRITER);
			if (this.writer != null)
			{
				try
				{
					exportReportToWriter();
				}
				catch (IOException e)
				{
					throw new JRException("Error writing to writer : " + this.jasperPrint.getName(), e);
				}
			}
			else
			{
				OutputStream os = (OutputStream)this.parameters.get(JRExporterParameter.OUTPUT_STREAM);
				if (os != null)
				{
					try
					{
						this.writer = new OutputStreamWriter(os, encoding);
						exportReportToWriter();
					}
					catch (IOException e)
					{
						throw new JRException("Error writing to OutputStream writer : " + this.jasperPrint.getName(), e);
					}
				}
				else
				{
					File destFile = (File)this.parameters.get(JRExporterParameter.OUTPUT_FILE);
					if (destFile == null)
					{
						String fileName = (String)this.parameters.get(JRExporterParameter.OUTPUT_FILE_NAME);
						if (fileName != null)
						{
							destFile = new File(fileName);
						}
						else
						{
							throw new JRException("No output specified for the exporter.");
						}
					}

					try
					{
						os = new FileOutputStream(destFile);
						this.writer = new OutputStreamWriter(os, encoding);
						exportReportToWriter();
					}
					catch (IOException e)
					{
						throw new JRException("Error writing to file writer : " + this.jasperPrint.getName(), e);
					}
					finally
					{
						if (this.writer != null)
						{
							try
							{
								this.writer.close();
							}
							catch(IOException e)
							{
							}
						}
					}
				}
			}
		}
	}


	/**
	 *
	 */
	protected void exportReportToWriter() throws JRException, IOException
	{
		for(int reportIndex = 0; reportIndex < this.jasperPrintList.size(); reportIndex++)
		{
			this.jasperPrint = (JasperPrint)this.jasperPrintList.get(reportIndex);

			List pages = this.jasperPrint.getPages();
			if (pages != null && pages.size() > 0)
			{
				if (this.isModeBatch)
				{
					this.startPageIndex = 0;
					this.endPageIndex = pages.size() - 1;
				}

				if (this.characterWidth > 0)
					this.pageWidth = this.jasperPrint.getPageWidth() / this.characterWidth;
				if (this.characterHeight > 0)
					this.pageHeight = this.jasperPrint.getPageHeight() / this.characterHeight;

				for(int i = this.startPageIndex; i <= this.endPageIndex; i++)
				{
					if (Thread.currentThread().isInterrupted())
					{
						throw new JRException("Current thread interrupted.");
					}

					JRPrintPage page = (JRPrintPage)pages.get(i);

					/*   */
					exportPage(page);
				}
			}
		}

		this.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 appropiate 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();

		this.pageData = new char[this.pageHeight][];
		for (int i = 0; i < this.pageHeight; i++) {
			this.pageData[i] = new char[this.pageWidth];
			Arrays.fill(this.pageData[i], ' ');
		}

		exportElements(elements);

		for (int i = 0; i < this.pageHeight; i++) {
			this.writer.write(this.pageData[i]);
			this.writer.write(this.lineSeparator);
		}

		this.writer.write(this.betweenPagesText);

		if (this.progressMonitor != null)
		{
			this.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 rowCount = calculateYCoord(element.getHeight());
		int columnCount = calculateXCoord(element.getWidth());
		int x = calculateXCoord(element.getX() + getOffsetX());
		int y = calculateYCoord(element.getY() + getOffsetY());
		
		if (x + columnCount > this.pageWidth) {
			//if the text exceeds the page width, truncate the column count
			columnCount = this.pageWidth - x;
		}

		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 (rowCount <= 0 || columnCount <= 0)
			return;

		if (allText != null && allText.length() == 0)
			return;

		// uses an array of string buffers, since the maximum number of rows is already calculated
		StringBuffer[] rows = new StringBuffer[rowCount];
		rows[0] = new StringBuffer();
		int rowIndex = 0;
		int rowPosition = 0;
		int newLine = 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("");
				isFirstLine = false;
				rowIndex++;
				if(rowIndex == rowCount || !lfTokenizer.hasMoreTokens())
					break label;
				rowPosition = 0;
				rows[rowIndex] = new StringBuffer();
				line = lfTokenizer.nextToken();
			}
			// 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 == rowCount)
						break label;
					rowPosition = 0;
					rows[rowIndex] = new StringBuffer();
					//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() > columnCount) {
						rows[rowIndex].append(word.substring(0, columnCount - rowPosition));
						word = word.substring(columnCount - rowPosition, word.length());
						rowIndex++;
						if(rowIndex == rowCount)
							break label;
						rowPosition = 0;
						rows[rowIndex] = new StringBuffer();
					}
	
					// situation: word is larger than remaining space on the current line
					// in this case, go to the next line
					if (rowPosition + word.length() > columnCount) {
						rowIndex++;
						if (rowIndex == rowCount)
							break label;
						rowPosition = 0;
						rows[rowIndex] = new StringBuffer();
					}
	
					// 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 > 9 && rowPosition == 0 && word.equals(" "))
						break;
	
					// 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 == rowCount)
					break;
				rowPosition = 0;
				rows[rowIndex] = new StringBuffer();
			}
		}

		int xOffset = 0;
		int yOffset = 0;

		if (element.getVerticalAlignment() == JRAlignment.VERTICAL_ALIGN_BOTTOM)
			yOffset = rowCount - rowIndex;
		if (element.getVerticalAlignment() == JRAlignment.VERTICAL_ALIGN_MIDDLE)
			yOffset = (rowCount - rowIndex) / 2;

		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);
			if (element.getHorizontalAlignment() == JRAlignment.HORIZONTAL_ALIGN_RIGHT)
				xOffset = columnCount - line.length();
			if (element.getHorizontalAlignment() == JRAlignment.HORIZONTAL_ALIGN_CENTER)
				xOffset = (columnCount - line.length()) / 2;

			// if text is justified, there is no offset, but the line text is modified
			// the last line in the paragraph is not justified.
			if (element.getHorizontalAlignment() == JRAlignment.HORIZONTAL_ALIGN_JUSTIFIED)
				if (i < rowIndex -1)
					line = justifyText(line, columnCount);

			char[] chars = line.toCharArray();
			System.arraycopy(chars, 0, this.pageData[y + yOffset + i], x + xOffset, chars.length);
		}
	}


	/**
	 * Justifies the text inside a specified space.
	 */
	private String justifyText(String s, int width)
	{
		StringBuffer justified = new StringBuffer();

		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 y coordinates from pixel space to character space.
	 */
	protected int calculateYCoord(int y)
	{
		return (int) (((long) this.pageHeight * y) / this.jasperPrint.getPageHeight());
	}

	/**
	 * Transforms x coordinates from pixel space to character space.
	 */
	protected int calculateXCoord(int x)
	{
		return this.pageWidth * x / this.jasperPrint.getPageWidth();
	}


	/**
	 *
	 */
	protected JRStyledText getStyledText(JRPrintText textElement)
	{
		return textElement.getStyledText(JRStyledTextAttributeSelector.NONE);
	}

}
