新接手一个打印终端的项目,要求可以打印A4格式的单据和 70mm * 40mm 规格的条码。
整体流程可分两种情况,
一种是将打印模板转换为pdf文档二进制数组,进而生成为pdf文档,保存到本地,然后再读取到程序中,打印,最后删除生成的pdf文档(不然随着打印次数的增多,本地磁盘岂不爆满);
另一种是省略保存中间步骤,直接将打印模板转换得到的pdf文档二进制数组用于程序打印。
显然,第二种情况较为简单,项目最后也是采用的这种。
先说打印A4的情况。
打印A4的情况比较简单,本地有一台惠普的A4打印,直接上代码。
package com.jiuqi.dna.gams.GXH.yndx.printing.util;
import java.awt.print.Book;
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import java.awt.print.PrinterJob;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.print.PrintService;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.printing.PDFPageable;
import org.apache.pdfbox.printing.PDFPrintable;
import org.apache.pdfbox.printing.Scaling;
import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Dispatch;
import com.jacob.com.Variant;
import net.sf.json.JSONObject;
/**
* 自助打印终端打印工具类
* @author wangjiao01
*
*/
public class PDFPrintUtil {
/**
* 获取临时生成的pdf文件路径
* @param pdfData
* @return
*/
public static String getNewPDFPath(byte[] pdfData) {
DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
String newPdfName = df.format(new Date());
String newPdfPath = "E:\\pdf\\" + newPdfName + ".pdf";// 随具体环境变化
OutputStream outputStream = null;
try {
outputStream = new FileOutputStream(newPdfPath);
outputStream.write(pdfData);
}catch(IOException e) {
e.printStackTrace();
}finally {
if(outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return newPdfPath;
}
/**
* 执行打印
* @param pdfData pdf文档对应的二进制数组
* @param printerName 打印机标识
* @param copyCount 打印份数
* @return
* @throws IOException
*/
public static String doPrintByPDFBox(byte[] pdfData, String printerName, Integer copyCount) throws IOException {
String result = null;
PDDocument document = null;
try {
document = PDDocument.load(pdfData);
PrinterJob printerJob = PrinterJob.getPrinterJob();
// 查找并设置打印机
PrintService[] printServices = PrinterJob.lookupPrintServices();
if(printServices == null || printServices.length == 0) {
result = getPrintMessage(false, "打印失败,计算机未安装打印机,请检查。");
// makeSound("打印失败,计算机未安装打印机,请检查。");
return result;
}
PrintService printService = null;
for(int i = 0; i < printServices.length; i++) {
if(printServices[i].getName().equalsIgnoreCase(printerName)) {
System.out.println(printServices[i].getName());
printService = printServices[i];
break;
}
}
if(printService != null) {
printerJob.setPrintService(printService);
} else {
result = getPrintMessage(false, "打印失败,未找到名称为" + printerName + "的打印机,请检查。");
// makeSound("打印失败,未找到名称为" + printerName + "的打印机,请检查。");
return result;
}
// 设置纸张
PDFPrintable pdfPrintable = new PDFPrintable(document, Scaling.ACTUAL_SIZE);
PageFormat pageFormat = new PageFormat();
pageFormat.setOrientation(PageFormat.PORTRAIT);
pageFormat.setPaper(getPaper(printerName));
// Book 的方式实现打印多张(已测试,可行)
Book book = new Book();
book.append(pdfPrintable, pageFormat, document.getNumberOfPages());
printerJob.setPageable(book);
// PDFPageable 的方式实现打印多张(未测试,应该也可行)
// PDFPageable pdfPageable = new PDFPageable(document);
// pdfPageable.append(pdfPrintable, pageFormat, document.getNumberOfPages());
// printerJob.setPageable(pdfPageable);
// 测试
System.out.println(document.getNumberOfPages());
System.out.println(book.getNumberOfPages());
// System.out.println(pdfPageable.getNumberOfPages());
// 执行打印
printerJob.setCopies(copyCount);
printerJob.print();
result = getPrintMessage(true, "打印成功。");
// makeSound("打印成功,请取件。");
} catch (Exception e) {
e.printStackTrace();
result = getPrintMessage(false, "打印失败:发生异常。");
// makeSound("打印失败,打印时发生异常,请检查。");
} finally {
if(document != null) {
document.close();// 起初文件删除失败,关闭文档之后,删除成功
}
}
return result;
}
/**
* 获取打印结果信息,成功或失败,用以返回前台界面
* @param isPrintSuccess
* @param message
* @return
*/
public static String getPrintMessage(boolean isPrintSuccess, String message) {
JSONObject object = new JSONObject();
if(isPrintSuccess) {
object.put("code", 1);
}else {
object.put("code", 0);
}
object.put("message", message);
System.out.println(message);
return object.toString();
}
/**
* 删除打印过程中创建的临时pdf文件
* @param newPdfPath
* @return
*/
public static boolean deleteFile(String newPdfPath) {
File file = new File(newPdfPath);
if(file.exists()) {
if(file.isFile()) {
return file.delete();
}
}else {
System.out.println("文件 " + newPdfPath + " 不存在!");
}
return false;
}
/**
* 打印语音提示:成功或失败,并提示失败原因
* @param message
*/
public static void makeSound(String message) {
ActiveXComponent sap = new ActiveXComponent("Sapi.SpVoice");
try {
// 音量 0-100
sap.setProperty("Volume", new Variant(100));
// 语音朗读速度 -10 到 +10
sap.setProperty("Rate", new Variant(0));
// 获取执行对象
Dispatch sapo = sap.getObject();
// 执行朗读
Dispatch.call(sapo, "Speak", new Variant(message));
// 关闭执行对象
sapo.safeRelease();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭应用程序连接
sap.safeRelease();
}
}
/**
* 根据打印机名称判断是单据打印还是条码打印,进而创建对应Paper对象并返回
* @param printerName
* @return
*/
public static Paper getPaper(String printerName) {
Paper paper = new Paper();
// 默认为A4纸张,对应像素宽和高分别为 595, 848
int width = 595;
int height = 848;
// 设置边距,单位是像素,10mm边距,对应 28px
int marginLeft = 10;
int marginRight = 0;
int marginTop = 10;
int marginBottom = 0;
if(printerName.contains("bar")) {
// 云南大学条码纸张规格70mm宽*40mm高,对应像素值为 198, 113
width = 198;
height = 113;
}
paper.setSize(width, height);
// 下面一行代码,解决了打印内容为空的问题
paper.setImageableArea(marginLeft, marginRight, width - (marginLeft + marginRight), height - (marginTop + marginBottom));
return paper;
}
}
经过探索与研究,对PDFBox的一些类有了一定的认识,现记录如下,以便大家今后使用。
1、PDDocument,对应一个实际的pdf文档,有好多个重载的静态load方法,用于将实际pdf文档创建到内存中。
getNumberOfPages方法可获取文档的总页数。
2、PDFPrintable,对应的API类注释为 Prints pages from a PDF document using any page size or scaling mode.
实际使用举例:
PDFPrintable pdfPrintable = new PDFPrintable(document, Scaling.ACTUAL_SIZE);
经常用以作为Book或Pageable类的方法参数。
3、PageFormat,API对应的类注释为 The PageFormat
class describes the size and orientation of a page to be printed. 用于格式化打印规格,如设置打印方向(横向和纵向)、打印纸张等。
实际使用举例:
pageFormat.setOrientation(PageFormat.PORTRAIT);// 设置打印方向为纵向。PageFormat.PORTRAIT表示纵向,PageFormat.LANDSCAPE表示横向
pageFormat.setPaper(getPaper(printerName));// 设置纸张规格
具体获取纸张方法代码如下:
/**
* 根据打印机名称判断是单据打印还是条码打印,进而创建对应Paper对象并返回
* @param printerName
* @return
*/
public static Paper getPaper(String printerName) {
Paper paper = new Paper();
// 默认为A4纸张,对应像素宽和高分别为 595, 848
int width = 595;
int height = 848;
// 设置边距,单位是像素,10mm边距,对应 28px
int marginLeft = 10;
int marginRight = 0;
int marginTop = 10;
int marginBottom = 0;
if(printerName.contains("bar")) {
// 云南大学条码纸张规格70mm宽*40mm高,对应像素值为 198, 113
width = 198;
height = 113;
}
paper.setSize(width, height);
// 下面一行代码,解决了打印内容为空的问题
paper.setImageableArea(marginLeft, marginRight, width - (marginLeft + marginRight), height - (marginTop + marginBottom));
return paper;
}
请务必注意调用Paper对象的setImageableArea方法,否则你将很惊喜地看到打印内容一片空白的现象。
4、Book,顾名思义,书,可以看做一个有多页的册子,每一页都可以有自己不同的纸张格式。PDFPrintable就是用于它的
append方法的第一个参数。append方法源代码如下:
/**
* Appends numPages
pages to the end of this
* Book
. Each of the pages is associated with
* page
.
* @param painter the Printable
instance that renders
* the page
* @param page the size and orientation of the page
* @param numPages the number of pages to be added to the
* this Book
.
* @throws NullPointerException
* If the painter
or page
* argument is null
*/
public void append(Printable painter, PageFormat page, int numPages) {
BookPage bookPage = new BookPage(painter, page);
int pageIndex = mPages.size();
int newSize = pageIndex + numPages;
mPages.setSize(newSize);
for(int i = pageIndex; i < newSize; i++){
mPages.setElementAt(bookPage, i);
}
}
book.append(pdfPrintable, pageFormat, document.getNumberOfPages()) 可实现打印多张,倘若用的它的没有第三个参数重载方法public void append(Printable painter, PageFormat page),那么你将惊喜地看到明明选择了三张,却只打印了一张的现象。
倘若代码里未曾设置纸张格式,则可能会看到内容截断、跨两页纸、纸张横向内容纵向等奇怪现象。
因为java默认的打印,会从打印机纸张里寻找相近的纸张进行匹配,如果没有添加自定义纸张,可能找出来的是别的纸张,而且,java读取纸张有个限制, 那就是默认纸张 高度 >= 宽度,高度 < 宽度的纸张是读取不到的。
原因在源代码里,请看,
/**
* {@inheritDoc}
*
* Returns the actual physical size of the pages in the PDF file. May not fit the local printer.
*/
@Override
public PageFormat getPageFormat(int pageIndex)
{
PDPage page = document.getPage(pageIndex);
PDRectangle mediaBox = PDFPrintable.getRotatedMediaBox(page);
PDRectangle cropBox = PDFPrintable.getRotatedCropBox(page);
// Java does not seem to understand landscape paper sizes, i.e. where width > height, it
// always crops the imageable area as if the page were in portrait. I suspect that this is
// a JDK bug but it might be by design, see PDFBOX-2922.
//
// As a workaround, we normalise all Page(s) to be portrait, then flag them as landscape in
// the PageFormat.
Paper paper;
boolean isLandscape;
if (mediaBox.getWidth() > mediaBox.getHeight())
{
// rotate
paper = new Paper();
paper.setSize(mediaBox.getHeight(), mediaBox.getWidth());
paper.setImageableArea(cropBox.getLowerLeftY(), cropBox.getLowerLeftX(),
cropBox.getHeight(), cropBox.getWidth());
isLandscape = true;
}
else
{
paper = new Paper();
paper.setSize(mediaBox.getWidth(), mediaBox.getHeight());
paper.setImageableArea(cropBox.getLowerLeftX(), cropBox.getLowerLeftY(),
cropBox.getWidth(), cropBox.getHeight());
isLandscape = false;
}
PageFormat format = new PageFormat();
format.setPaper(paper);
// auto portrait/landscape
switch (orientation)
{
case AUTO:
format.setOrientation(isLandscape ? PageFormat.LANDSCAPE : PageFormat.PORTRAIT);
break;
case LANDSCAPE:
format.setOrientation(PageFormat.LANDSCAPE);
break;
case PORTRAIT:
format.setOrientation(PageFormat.PORTRAIT);
break;
default:
break;
}
return format;
}
这是PDFPageable重写的Book的方法,获取纸张格式,该方法在纸张宽度大于高度的时候进行了调换,将宽度给了高度,高度给了宽度,相当于顺时针或逆时针旋转了90度。我打印条码的时候就遇到了这个问题,条码规格是宽70毫米,高40毫米,打印机纸张是横向的,但是内容总是纵着出来,无论怎么设置,打印模板横纵切换,还是打印机打印方向横纵切换,都没有效果,就是这段代码搞的鬼。这是我忽略了设置纸张格式导致的,请大家今后务必注意。
当然,仅在代码里设置纸张格式是不够的,打印机里一定要确实有这种纸张格式才行。否则,打印机又会智能地寻找最相近的纸张来忽悠你了。
解决问题过程中,有参考如下博客文档,写的很好,推荐下。
http://www.cnblogs.com/xdecode/p/7905041.html