/*
* 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 - 2009 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.modules.output.pageable.plaintext.driver;
import java.awt.print.Paper;
import java.io.IOException;
import java.io.OutputStream;
import org.pentaho.reporting.engine.classic.core.modules.output.pageable.plaintext.helper.EncodingUtilities;
import org.pentaho.reporting.engine.classic.core.modules.output.pageable.plaintext.helper.PrinterSpecification;
import org.pentaho.reporting.engine.classic.core.modules.output.pageable.plaintext.helper.PrinterSpecificationManager;
import org.pentaho.reporting.engine.classic.core.util.PageFormatFactory;
public abstract class AbstractEpsonPrinterDriver implements PrinterDriver
{
public static final String OP_NO_ASSIGN_CHAR_TABLE = "no.assign.character.table";
protected static class DriverState
{
private boolean bold;
private boolean underline;
private boolean italic;
private boolean strikethrough;
private byte font;
private int manualLeftBorder;
protected DriverState()
{
}
public boolean isBold()
{
return bold;
}
public void setBold(final boolean bold)
{
this.bold = bold;
}
public boolean isItalic()
{
return italic;
}
public void setItalic(final boolean italic)
{
this.italic = italic;
}
public boolean isUnderline()
{
return underline;
}
public void setUnderline(final boolean underline)
{
this.underline = underline;
}
public byte getFont()
{
return font;
}
public void setFont(final byte font)
{
this.font = font;
}
public int getManualLeftBorder()
{
return manualLeftBorder;
}
public void setManualLeftBorder(final int manualLeftBorder)
{
this.manualLeftBorder = manualLeftBorder;
}
public boolean isStrikethrough()
{
return strikethrough;
}
public void setStrikethrough(final boolean strikethrough)
{
this.strikethrough = strikethrough;
}
public void reset()
{
this.bold = false;
this.italic = false;
this.strikethrough = false;
this.underline = false;
this.font = PrinterDriverCommands.SELECT_FONT_ROMAN;
this.manualLeftBorder = 0;
}
}
public static final String FONT_15_CPI = "Epson.Font-15CPI-available";
private PrinterSpecification printerSpecification;
private FontMapper fontMapper;
private OutputStream out;
private float charsPerInch;
private float linesPerInch;
private EncodingUtilities encodingUtilities;
private boolean firstPage;
private byte fallBackCharset;
//private int borderTop;
//private int borderBottom;
private DriverState driverState;
private String encoding;
protected AbstractEpsonPrinterDriver(final OutputStream out,
final float charsPerInch,
final float linesPerInch,
final String printerModel)
{
if (out == null)
{
throw new NullPointerException();
}
if (printerModel == null)
{
throw new NullPointerException();
}
this.out = out;
this.charsPerInch = charsPerInch;
this.linesPerInch = linesPerInch;
this.printerSpecification = lookupPrinterSpecification(printerModel);
this.fontMapper = new DefaultFontMapper();
this.firstPage = true;
this.driverState = new DriverState();
//validate the CPI values
if (isValidCPI(charsPerInch) == false)
{
throw new IllegalArgumentException
("The given CPI of '" + charsPerInch +
"' is invalid for the selected printer model ('" + printerModel + "'.");
}
// we cannot influence the LPI, so we have to accept what the user gives in
}
private boolean isValidCPI(final float charsPerInch)
{
if (charsPerInch == PrinterDriverCommands.CPI_10)
{
return true;
}
if (charsPerInch == PrinterDriverCommands.CPI_12)
{
return true;
}
if (charsPerInch == PrinterDriverCommands.CPI_17)
{
return true;
}
if (charsPerInch == PrinterDriverCommands.CPI_20)
{
return true;
}
if (charsPerInch == PrinterDriverCommands.CPI_15 &&
getPrinterSpecification().isFeatureAvailable(AbstractEpsonPrinterDriver.FONT_15_CPI))
{
return true;
}
return false;
}
public AbstractEpsonPrinterDriver.DriverState getDriverState()
{
return driverState;
}
public FontMapper getFontMapper()
{
return fontMapper;
}
public void setFontMapper(final FontMapper fontMapper)
{
if (fontMapper == null)
{
throw new NullPointerException();
}
this.fontMapper = fontMapper;
}
protected OutputStream getOut()
{
return out;
}
protected boolean isFirstPage()
{
return firstPage;
}
public PrinterSpecification getPrinterSpecification()
{
return printerSpecification;
}
/**
* Ends a new line.
*
* @param overflow
* @throws java.io.IOException if an IOError occures.
*/
public void endLine(final boolean overflow)
throws IOException
{
if (overflow == false)
{
out.write(PrinterDriverCommands.CARRIAGE_RETURN);
out.write(PrinterDriverCommands.LINE_FEED);
}
else
{
out.write(0x7F);
}
}
/**
* Ends the current page. Should print empty lines or an FORM_FEED command.
*
* @param overflow
* @throws java.io.IOException if there was an IOError while writing the command
*/
public void endPage(final boolean overflow)
throws IOException
{
if (overflow == false)
{
out.write(PrinterDriverCommands.FORM_FEED);
}
}
/**
* Gets the default character width in CPI.
*
* @return the default character width in CPI.
*/
public float getCharactersPerInch()
{
return charsPerInch;
}
/**
* Gets the default line height.
*
* @return the default line height.
*/
public float getLinesPerInch()
{
return linesPerInch;
}
/**
* Flushes the output stream.
*
* @throws java.io.IOException if an IOError occured.
*/
public void flush()
throws IOException
{
out.flush();
}
/**
* Prints a single text chunk at the given position on the current line. The chunk should not be printed, if an
* previous chunk overlays this chunk.
*
* @param chunk the chunk that should be written
* @throws java.io.IOException if an IO error occured.
*/
public void printChunk(final PlaintextDataChunk chunk)
throws IOException
{
final String text = chunk.getText().substring(0, chunk.getWidth());
final String fd = chunk.getFont();
sendDefineFont(fontMapper.getPrinterFont(fd));
sendFontStyle(chunk.isBold(), chunk.isItalic(), chunk.isUnderline(), chunk.isStrikethrough());
getEncodingUtilities(encoding).writeEncodedText(text, out);
}
protected abstract void sendFontStyle(boolean bold, boolean italic,
boolean underline, boolean strikeTrough)
throws IOException;
protected void sendDefineFont(final byte printerFont)
throws IOException
{
if (getDriverState().getFont() != printerFont)
{
getOut().write(0x1b);
getOut().write(0x6b);
getOut().write(printerFont);
getDriverState().setFont(printerFont);
}
}
/**
* Prints an empty chunk. This is called for all undefined chunk-cells. The last defined font is used to print that
* empty text.
*
* @throws java.io.IOException if an IOError occured.
*/
public void printEmptyChunk(final int count)
throws IOException
{
sendFontStyle(getDriverState().isBold(), getDriverState().isItalic(), false, false);
for (int i = 0; i < count; i++)
{
out.write(PrinterDriverCommands.SPACE);
}
}
/**
* Prints some raw content. This content is not processed in any way, so be very carefull.
*
* @param raw the content that should be printed.
*/
public void printRaw(final byte[] raw)
throws IOException
{
out.write(raw);
}
/**
* Starts a new line.
*
* @throws java.io.IOException if an IOError occures.
*/
public void startLine()
throws IOException
{
sendFontStyle(false, false, false, false);
final int manualLeftBorder = getDriverState().getManualLeftBorder();
for (int i = 0; i < manualLeftBorder; i++)
{
out.write(PrinterDriverCommands.SPACE);
}
}
/**
* Resets the printer and starts a new page. Prints the top border lines (if necessary).
*
* @throws java.io.IOException if there was an IOError while writing the command
*/
public void startPage(final Paper paper, final String encoding)
throws IOException
{
this.encoding = encoding;
final float lineHeightPoints = 72.0f / getLinesPerInch();
final float charWidthPoints = 72.0f / getCharactersPerInch();
// Quoted from the Epson Reference Manual page R-4:
// 1. Send an ESC @ to initialize the printer
sendResetPrinter();
driverState.reset();
// 2. Set the unit of line spacing to the minimum vertical increment necessary
sendDefineLineSpacing(lineHeightPoints);
// 3. Set the printing area
final int lines = (int) ((paper.getHeight() / 72.0f) * getLinesPerInch());
sendDefinePageLengthInLines(lines);
sendDefineCharacterWidth(getCharactersPerInch());
final PageFormatFactory fact = PageFormatFactory.getInstance();
final int borderLeft = (int) (fact.getLeftBorder(paper) / charWidthPoints);
final int borderRight = (int) (fact.getRightBorder(paper) / charWidthPoints);
sendDefineHorizontalBorders(borderLeft, borderRight);
final int borderTop = (int) (fact.getTopBorder(paper) / lineHeightPoints);
//borderBottom = (int) (fact.getBottomBorder(paper) / lineHeightPoints);
// print the top margin ..
for (int i = 0; i < borderTop; i++)
{
startLine();
endLine(false);
}
// 4. Assign character tables to each of the four active tables as
// necessary. (ESC/P2 printers only)
//
// this is done before we start printing the text (and may change mid-page).
// sendDefineCodepage(encoding);
// 5. Define any user-defined characters.
//
// this is done before we start printing the text (and may change mid-page).
// sendDefineUserCharacters();
}
protected void sendDefineCharacterWidth(final float charactersPerInch)
throws IOException
{
if (charactersPerInch == PrinterDriverCommands.CPI_10)
{
getOut().write(0x12); // disable condensed printing
getOut().write(0x1b);
getOut().write(0x50); // select 10 CPI
}
else if (charactersPerInch == PrinterDriverCommands.CPI_12)
{
getOut().write(0x12); // disable condensed printing
getOut().write(0x1b);
getOut().write(0x4d); // select 12 CPI
}
else if (charactersPerInch == PrinterDriverCommands.CPI_15)
{
// All ESC/P2 and 24Pin ESC/P printers support that mode
// Additionally, the 9Pin printer models FX-2170 and DFX-5000+
// support that character width.
getOut().write(0x12); // disable condensed printing
getOut().write(0x1b);
getOut().write(0x67);
}
else if (charactersPerInch == PrinterDriverCommands.CPI_17)
{
getOut().write(0x0f); // enable condensed printing
getOut().write(0x1b);
getOut().write(0x50); // select 10 CPI (-> 17.14 cpi because of condensed printing)
}
else if (charactersPerInch == PrinterDriverCommands.CPI_20)
{
getOut().write(0x0f); // enable condensed printing
getOut().write(0x1b);
getOut().write(0x4d); // select 12 CPI (-> 20 cpi because of condensed printing)
}
else
{
throw new IllegalArgumentException("The given character width is invalid");
}
}
protected void sendResetPrinter()
throws IOException
{
out.write(0x1b);
out.write(0x40);
}
protected abstract void sendDefineLineSpacing(float lineHeightInPoints)
throws IOException;
protected void sendDefinePageLengthInLines(final int paperSizeInLines)
throws IOException
{
// SideEffects: Setting the page size will mark the current position
// as TopOfForm position
//
// All printers support that command.
getOut().write(0x1b); // ESC
getOut().write(0x43); // C
getOut().write(paperSizeInLines);
}
protected void sendDefineHorizontalBorders(final int left, final int right)
throws IOException
{
if (left < 256)
{
// depends on the pitch to be defined correctly.
// In that implementation we can assume that this is ok.
getOut().write(0x1b); // ESC
getOut().write(0x6c); // l
getOut().write(left);
getDriverState().setManualLeftBorder(0);
}
else
{
getOut().write(0x1b); // ESC
getOut().write(0x6c); // l
getOut().write(255);
getDriverState().setManualLeftBorder(left - 255);
}
// Compatibility: All printers support that command.
// don't care about the right border, the PlainTextPage makes sure that
// we do not violate that constraint.
}
protected void sendDefineCodepage(final String encoding, final int characterTable)
throws IOException
{
if (getPrinterSpecification().isFeatureAvailable(AbstractEpsonPrinterDriver.OP_NO_ASSIGN_CHAR_TABLE))
{
out.write(0x1b); // ESC
out.write(0x52); // R
out.write(getFallBackCharset());
}
else
{
final byte[] cp = getPrinterSpecification().getEncoding(encoding).getCode();
out.write(0x1b); // ESC
out.write(0x28); // (
out.write(0x74); // t
out.write(0x03); // const: 3
out.write(0x00); // const: 0
out.write(characterTable); // Define charset; (0 works on all printers)
out.write(cp); // the codepage
}
out.write(0x1b); // ESC
out.write(0x74); // t
out.write(0x00); // Select charset 0 (works on all printers)
}
protected void sendDefineUserCharacters()
{
}
protected EncodingUtilities getEncodingUtilities(final String encoding)
throws IOException
{
if (encodingUtilities != null &&
encodingUtilities.getEncoding().equals(encoding))
{
return encodingUtilities;
}
encodingUtilities = new EncodingUtilities(encoding);
sendDefineCodepage(encoding, 0);
return encodingUtilities;
}
protected abstract PrinterSpecificationManager getPrinterSpecificationManager();
private PrinterSpecification lookupPrinterSpecification(final String model)
{
final PrinterSpecificationManager printerSpecificationManager =
getPrinterSpecificationManager();
if (model == null)
{
return PrinterSpecificationManager.getGenericPrinter();
}
final PrinterSpecification printerModel =
printerSpecificationManager.getPrinter(model);
if (printerModel == null)
{
throw new IllegalArgumentException("The printer model is not supported.");
}
return printerModel;
}
public byte getFallBackCharset()
{
return fallBackCharset;
}
public void setFallBackCharset(final byte fallBackCharset)
{
this.fallBackCharset = fallBackCharset;
}
}