上一篇中,我们已经通过iText生成了共打印的pdf文档,如果没有特别的要求,利用浏览器的pdf插件来预览和打印已经能供满足我们Web应用在浏览器端实现票据套打的要求了。但在我们的项目中,客户要求一次操作之后连续进行打印,即使用不同的数据填充模板,连续套打上百张的票据。
当时考虑的实现方式有两种:
- 生成一个多页的pdf文档,还是利用浏览器的pdf插件来进行打印
- 生成多个pdf文档,使用applet利用java print service来进行浏览器端的连续打印
最终我们选择了第二种方案,好处是浏览器不用打开新的tab来显示pdf文档,用户的注意力可以始终停留在同一页面。此外打印选项可以直接显示在页面中,比较直观。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.print.*;import java.io.IOException;
import java.io.InputStream;import java.nio.ByteBuffer;import javax.print.PrintService;
import javax.print.PrintServiceLookup;import com.sun.pdfview.PDFFile;
import com.sun.pdfview.PDFPage;
import com.sun.pdfview.PDFPrintPage;public class PDFPrintApplet extends JApplet implements ActionListener {/*** */private static final long serialVersionUID = 6293078385198700265L;private PrintService[] services;private boolean degugFlg = true;private PrinterJob pjob = PrinterJob.getPrinterJob();private JComboBox printerList;private JButton printBtn;public void init() {// 创建applet的GUI,由event dispatching thread运行try {SwingUtilities.invokeAndWait(new Runnable() {public void run() {createGUI();}});} catch (Exception e) {System.err.println("createGUI didn't complete successfully");}}//简单的打印选项UI,只包括选择打印机,可以根据需要添加更多的选项private void createGUI() {JLabel lbl = new JLabel("选择打印机: ");String[] printerServiceNames = getAllAvailablePrintServicesNames();printerList = new JComboBox(printerServiceNames);printerList.addActionListener(this);JPanel printerSelectorPanel = new JPanel(new FlowLayout());printerSelectorPanel.setBackground(Color.WHITE);printerSelectorPanel.add(lbl);printerSelectorPanel.add(printerList);JPanel buttonPanel = new JPanel();buttonPanel.setBackground(Color.WHITE);printBtn = new JButton("打印");printBtn.addActionListener(this);buttonPanel.add(printBtn);setLayout(new GridLayout(2, 1));add(printerSelectorPanel);add(buttonPanel);}public void actionPerformed(ActionEvent e) {if (e.getSource() == printBtn) {launchPrint();} else if (e.getSource() == printerList) {JComboBox cb = (JComboBox) e.getSource();String printerName = (String) cb.getSelectedItem();setSelectedPrintService(printerName);}}//得到要打印的pdf文件的url列表,并开始打印private void launchPrint() {final List<String> inputArr = convertArrayParameter(getParameter("pdfFileNameArray"));try {List<URL> pdfInputStreamList = load(inputArr); print(pdfInputStreamList);} catch (MalformedURLException e) {e.printStackTrace();} catch (URISyntaxException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}// 根据传给Applet的参数,下载相应的PDF文件,生成URLprivate List<URL> load(List<String> fileNameList)throws URISyntaxException, IOException {List<URL> pdfURLList = new ArrayList<URL>();for (String fileName : fileNameList) {URL url = new URL(fileName);pdfURLList.add(url);}return pdfURLList;}// 解析传给Applet的参数,将以逗号分隔的字符串分成参数Listprivate List<String> convertArrayParameter(String parameterArrStr) {List<String> list = new ArrayList<String>();String[] parameterArr = parameterArrStr.split(",");Collections.addAll(list, parameterArr);return list;}// 将InputStream转为ByteBufferprivate ByteBuffer getByteBufferFormInputStream(InputStream is)throws IOException {int BUFSIZE = 1024;List<Byte> byteV = new ArrayList<Byte>();byte[] tmp1 = new byte[BUFSIZE];while (true) {int r = is.read(tmp1, 0, BUFSIZE);if (r == -1)break;for (int i = 0; i < r; i++) {byteV.add(new Byte(tmp1[i]));}}byte[] tmp2 = new byte[byteV.size()];for (int i = 0; i < byteV.size(); i++) {tmp2[i] = byteV.get(i).byteValue();}return ByteBuffer.wrap(tmp2);}// 得到本地所有可用的打印服务名private String[] getAllAvailablePrintServicesNames() {services = PrintServiceLookup.lookupPrintServices(null, null);if (services.length == 0) {}String[] printServicesNames = new String[services.length];for (int i = 0; i < services.length; i++) {printServicesNames[i] = services[i].getName();}return printServicesNames;}// 设置要使用的打印服务private void setSelectedPrintService(String serviceName) {try {for (PrintService service : services) {if (service.getName().equals(serviceName)) {pjob.setPrintService(service);return;}}} catch (PrinterException e) {// TODO Auto-generated catch blocke.printStackTrace();}}private void setDefaultPrintService() {services = PrintServiceLookup.lookupPrintServices(null, null);while (services.length == 0) {services = PrintServiceLookup.lookupPrintServices(null, null);}int j = 0;for (PrintService ps : services) {if (ps.getName().equals("doPDF v7")) {try {pjob.setPrintService(services[j]);} catch (PrinterException e) {// TODO Auto-generated catch blocke.printStackTrace();}} else {j++;}}}//执行打印private void print(List<URL> pdfURLList) throws IOException {if (pjob.getPrintService() == null && degugFlg == true) {setDefaultPrintService();}for (URL pdfURL : pdfURLList) {ByteBuffer buf = getByteBufferFormInputStream(pdfURL.openStream());PDFFile pdfFile = new PDFFile(buf);PDFPrintPage pages = new PDFPrintPage(pdfFile);PDFPage page = pdfFile.getPage(1);PageFormat pf = pjob.defaultPage();pf.setOrientation(PageFormat.PORTRAIT);Paper paper = new Paper();paper.setSize(page.getWidth(), page.getHeight());// This is to fix an error in PDF-Renderer// View// http://juixe.com/techknow/index.php/2008/01/17/print-a-pdf-document-in-java/// for details// Printing a PDF is also possible by sending the bytes directly to// the printer, but// the printer would have to support it.paper.setImageableArea(0, 0, paper.getWidth() * 2,paper.getHeight());Book book = new Book();pf.setPaper(paper);book.append(pages, pf, 1);pjob.setPageable(book);try {pjob.print();} catch (PrinterException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
}
上面是applet代码,通过Java Network Launch Protocol (JNLP) 协议来部署applet,并传入数据,JS代码如下:
var attributes = {code:'PDFPrintApplet',width:500, height:200} ;
var jnlp_path= '<%=basePath%>applet/pdfprintapplet.jnlp';
var parameters = {jnlp_href : jnlp_path,//这个参数就是传递给applet的待打印pdf文件的url列表pdfFileNameArray : array
};
var version = '1.6';
deployJava.runApplet(attributes, parameters, version);