PDFBox打印PDF A4格式文档和定制规格条码实例

新接手一个打印终端的项目,要求可以打印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

 

你可能感兴趣的:(Java)