前言:
1、需要少量硬件知识 需要懂IO流
2、作为总结 内容有点多
3、打印机分为串口打印、网口打印、并口打印等,一般情况 只有指令打印才需要去区分,而驱动打印的话 ,只需要知道打印机名字就好了 Printable+awt+javax.print
4、一般情况 热敏打印机 驱动和指令两种打印方式 速度基本差不多 因为都很快 ;而老旧的针式打印机 驱动打印会特别慢,或者热敏打印 但不适合装驱动时 需要指令打印。
一、驱动打印:
// google的core 包,用于网址转二维码 不需要相应代码可删除
import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import javax.imageio.ImageIO;
import javax.print.PrintServiceLookup;
import javax.print.attribute.HashAttributeSet;
import javax.print.attribute.standard.PrinterName;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.MemoryImageSource;
import java.awt.image.PixelGrabber;
import java.awt.print.*;
import java.io.*;
/**
*@author by qkj
*/
public class PrintDemo {
public static void main(String[] args)
{
// 可以理解为新建一个打印面板
Book book = new Book();
PageFormat pf = new PageFormat();
pf.setOrientation(1);
// 打印纸张 以及设置规格等
Paper p = new Paper();
p.setSize(250, 750);
p.setImageableArea(0.0D, 0.0D, 250, 750);
pf.setPaper(p);
Printable printable = new Printable(){
@Override
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
//Graphics是抽象类 ,多态 转型
Graphics2D g2 = (Graphics2D)graphics;
BufferedImage logo = null;
try {
// 图片的路径 这里是取的项目根目录 绝对路径
//(以及某些需要打包成exe的java项目 一定要用绝对路径 不能取src底下根目录)
String strBmpFile = System.getProperty("user.dir")
+ "\\logo1.bmp";
InputStream is = new FileInputStream(strBmpFile);
logo = ImageIO.read(is);
} catch (IOException e) {
e.printStackTrace();
}
// 打印图片
g2.drawImage(logo, (BufferedImageOp)null, 25, 10);
//打印文本
g2.drawString(" - - - - - - - - - - - - - - - - - - ", 20.0F, 100.0F);
g2.drawString("测试: 你好", 25.0F, 115.0F);
g2.drawString("测试: 你好" , 25.0F, 130.0F);
g2.drawString("测试: 你好" , 25.0F, 145.0F);
g2.drawString("测试: 你好" , 25.0F, 160.0F);
g2.drawString("测试: 你好" , 25.0F, 175.0F);
g2.drawString("测试: 你好" , 25.0F, 190.0F);
g2.drawString("测试: 你好" , 25.0F, 205.0F);
g2.drawString("测试: 你好:测试", 25.0F, 220.0F);
g2.drawString("测试: 你好:测试" , 25.0F, 235.0F);
g2.drawString("测试: 你好时间:10:00" , 25.0F, 250.0F);
g2.drawString("测试: 你好时间:10:00" , 25.0F, 265.0F);
// 网址转二维码
BufferedImage image = null;
try {
BitMatrix byteMatrix = new MultiFormatWriter().encode(new String("www.baidu.com".getBytes(), "iso-8859-1"),
BarcodeFormat.QR_CODE, 175, 175);
int width = byteMatrix.getWidth();
int height = byteMatrix.getHeight();
final int BLACK = 0xFF000000;
final int WHITE = 0xFFFFFFFF;
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, byteMatrix.get(x, y) ? BLACK : WHITE);
}
}
} catch (WriterException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 图片转单色位 需要则取之 某些老旧针式打印机中 指令打印只能打单色位bmp图片
String path = System.getProperty("user.dir") + "\\code.bmp";
File file = new File(path);
int h = 0;
int w = 0;
int[] pixels = new int[0];
try {
OutputStream fileOutputStream = new FileOutputStream(file);
ImageIO.write(image, "bmp", fileOutputStream);
fileOutputStream.close();
BufferedImage sourceImg = ImageIO.read(new File(path));
h = sourceImg.getHeight();
w = sourceImg.getWidth();
pixels = new int[w * h];
PixelGrabber pixelGrabber = new PixelGrabber(sourceImg, 0, 0, w, h, pixels, 0, w);
pixelGrabber.grabPixels();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
int gray;
for (int j = 0; j < h; j++) {
for (int i1 = 0; i1 < w; i1++) { // 由红,绿,蓝值得到灰度值
gray = (int) (((pixels[w * j + i1] >> 16) & 0xff) * 0.8);
gray += (int) (((pixels[w * j + i1] >> 8) & 0xff) * 0.1);
gray += (int) (((pixels[w * j + i1]) & 0xff) * 0.1);
pixels[w * j + i1] = (255 << 24) | (gray << 16) | (gray << 8) | gray;
}
}
MemoryImageSource imageSource = new MemoryImageSource(w, h, pixels, 0, w);
Image newimage = Toolkit.getDefaultToolkit().createImage(imageSource);
BufferedImage bufImage = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_BINARY);
bufImage.createGraphics().drawImage(newimage, 0, 0, null);
try {
ImageIO.write(bufImage, "BMP", new File(path));
} catch (IOException e) {
e.printStackTrace();
}
g2.drawImage(bufImage, (BufferedImageOp)null, 15, 335);
return 0;
}
};
book.append(printable, pf);
// 获取打印服务对象
PrinterJob job = PrinterJob.getPrinterJob();
job.setPageable(book);
try {
HashAttributeSet hs = new HashAttributeSet();
// 实际使用一定要写配置文件 不能直接写死打印机名字
//打印机名字为控制面板-打印机 属性里面的全名,例如A打印机 使用的是B打印机通用驱动,名字以B为准
String printerName = "TP805";
hs.add(new PrinterName(printerName, null));
PrintService[] pss = PrintServiceLookup.lookupPrintServices(null, hs);
if (pss.length == 0 /*|| pss1.length == 0*/) {
System.out.println("无法找到打印机:" + printerName);
}
// 设置打印类
job.setPageable(book);
//添加指定的打印机
job.setPrintService(pss[0]);
// 打印
job.print();
} catch (PrinterException e) {
e.printStackTrace();
}
}
}
二、难点:指令打印
首先需要知道:打印机有网口(类似于网线口 但是通常都是比网线口小一点),串口 (COM 上4下5 九个点 梯形状),并口(LPT 比串口长很多的梯形状),USB口 …
有的电脑主机可能没有串口,那么需要使用USB转串线 (电脑需要安装usb转串的驱动),效果等同于串口。
- 串口
需要下载RXTX一个jar包,两个dll文件,dll严格区分电脑32,,64位!并将其放入对应目录下。
优点:指令打印速度远大于驱动打印;
缺点:需要配置环境,如果用户规模大 且用户完全不懂技术 需要自行考虑时间与后果 是否能够承受
package printDemo;
// rxtx包
import gnu.io.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.TooManyListenersException;
/**
*@author by qkj
*/
public class NewPrintDemo implements SerialPortEventListener {
private static boolean isOpen = false;
static Set<CommPortIdentifier> portList = new HashSet<CommPortIdentifier>();
final static String appName = "MyApp";
private static InputStream is;
private static OutputStream os;
private static SerialPort serialPort;
byte[] readBuffer = new byte[100];
static OutputStream outputStream;
public static void main(String[] args) {
Enumeration tempPortList; //枚举类
CommPortIdentifier portIp;
tempPortList = CommPortIdentifier.getPortIdentifiers();
while (tempPortList.hasMoreElements()) {
//在这里可以调用getPortType方法返回端口类型,串口为CommPortIdentifier.PORT_SERIAL
portIp = (CommPortIdentifier) tempPortList.nextElement();
portList.add(portIp);
int portType = portIp.getPortType();
// 得到串口类型,并且串口名字为 XXX com6表示 串口接入电脑的com6口,(在设备管理器可查看)
if (portType == (CommPortIdentifier.PORT_SERIAL) && portIp.getName().equals("COM6")) {
try {
// com口以及超时时间(毫秒)
// 注:这里很可能报错 unknown application 实际意义为 串口打开失败 目前通过测试
//已知原因有: 1.串口占用,例如你写两个demo程序,另一个还开着占用了该串口 一定会报错;
//2.串口识别失败,比如插入电脑 没反应 或者串口坏了 这涉及到硬件问题 无法深一步解释
serialPort = (SerialPort) portIp.open("COM6", 5000);
// 显而易见 9600是打印机默认波特率
serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
// 输出流
outputStream = serialPort.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(SimpleWrite.outputStream, "UTF-8");
byte[] cmd = new byte[3];
cmd[0] = 0x1B;
cmd[1] = 'J';
cmd[2] = 0x0D;
outputStream.write(cmd);
outputStream.close();
// SimpleWrite.outputStream.write(cmd);
// writer.write("\n\n");
// writer.write(" 单号:11111\n");
//图片打印失败,打印出来内容实际为图片用文本格式打开的乱码内容,暂时不清楚怎么通过该IO
//方式打印 不知道是自身对IO理解错误还是和打印机本身有关,但需要注意的是 很多针式老款打
//印机 只能打印单色位图片,一定要严格转成单色位
// ImageIO.write(read, "jpg", outputStream);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public Set<CommPortIdentifier> getPortList() {
Enumeration tempPortList; //枚举类
CommPortIdentifier portIp;
tempPortList = CommPortIdentifier.getPortIdentifiers();
/*不带参数的getPortIdentifiers方法获得一个枚举对象,该对象又包含了系统中管理每个端口的CommPortIdentifier对象。
* 注意这里的端口不仅仅是指串口,也包括并口。
* 这个方法还可以带参数。
* getPortIdentifiers(CommPort)获得与已经被应用程序打开的端口相对应的CommPortIdentifier对象。
* getPortIdentifier(String portName)获取指定端口名(比如“COM1”)的CommPortIdentifier对象。
*/
while (tempPortList.hasMoreElements()) {
//在这里可以调用getPortType方法返回端口类型,串口为CommPortIdentifier.PORT_SERIAL
portIp = (CommPortIdentifier) tempPortList.nextElement();
portList.add(portIp);
}
return portList;
}
public boolean openSerialPort(CommPortIdentifier portIp, int delay) {
try {
serialPort = (SerialPort) portIp.open(appName, delay);
/* open方法打开通讯端口,获得一个CommPort对象。它使程序独占端口。
* 如果端口正被其他应用程序占用,将使用 CommPortOwnershipListener事件机制,传递一个PORT_OWNERSHIP_REQUESTED事件。
* 每个端口都关联一个 InputStream 和一个OutputStream。
* 如果端口是用open方法打开的,那么任何的getInputStream都将返回相同的数据流对象,除非有close 被调用。
* 有两个参数,第一个为应用程序名;第二个参数是在端口打开时阻塞等待的毫秒数。
*/
} catch (PortInUseException e) {
return false;
}
try {
is = serialPort.getInputStream();/*获取端口的输入流对象*/
os = serialPort.getOutputStream();/*获取端口的输出流对象*/
} catch (IOException e) {
return false;
}
try {
serialPort.addEventListener(this);/*注册一个SerialPortEventListener事件来监听串口事件*/
} catch (TooManyListenersException e) {
return false;
}
serialPort.notifyOnDataAvailable(true);/*数据可用*/
try {
/*设置串口初始化参数,依次是波特率,数据位,停止位和校验*/
serialPort.setSerialPortParams(4800, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
} catch (UnsupportedCommOperationException e) {
return false;
}
return true;
}
public boolean closeSerialPort() {
if (isOpen) {
try {
is.close();
os.close();
serialPort.notifyOnDataAvailable(false);
serialPort.removeEventListener();
serialPort.close();
isOpen = false;
} catch (IOException e) {
return false;
}
}
return true;
}
public boolean sendMessage(String message) {
try {
os.write(message.getBytes());
} catch (IOException e) {
return false;
}
return true;
}
@Override
public void serialEvent(SerialPortEvent event) {
/*
* 此处省略一下事件,可酌情添加
* SerialPortEvent.BI:/*Break interrupt,通讯中断
* SerialPortEvent.OE:/*Overrun error,溢位错误
* SerialPortEvent.FE:/*Framing error,传帧错误
* SerialPortEvent.PE:/*Parity error,校验错误
* SerialPortEvent.CD:/*Carrier detect,载波检测
* SerialPortEvent.CTS:/*Clear to send,清除发送
* SerialPortEvent.DSR:/*Data set ready,数据设备就绪
* SerialPortEvent.RI:/*Ring indicator,响铃指示
* SerialPortEvent.OUTPUT_BUFFER_EMPTY:/*Output buffer is empty,输出缓冲区清空
*/
if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
/*Data available at the serial port,端口有可用数据。读到缓冲数组,输出到终端*/
try {
while (is.available() > 0) {
is.read(readBuffer);//收到的数据再此,可视情况处理
}
} catch (IOException e) {
}
}
}
}
2.网口,和串口打印基本相似,需要在电脑配置相应的IP地址,换成socket获取输出流,指令数组放入输出流即可
3.补充 :通过其它方式:
有部分打印机厂商会提供dll文件以及相应demo, dll里面也是通过指令方式打印,并且会提供打印位图的方法,因为指令大多数与爱普生的一致,所以大部分打印机都是通用的,但可能存在部分兼容性问题,比如打印不全 ,没出问题还好 出现问题很难解决!! 如果有需要可以直接联系相关打印机厂商,涉及商业层面 代码以及dll不公开!