系统:Win10
Java:1.8.0_333
IDEA:2020.3.4
Gitee:https://gitee.com/lijinjiang01/Printer
在工作中,有次收到一位同事给我提的建议,说公司的CS系统,能不能直接调用打印机打印PDF,因为我们的系统之前是将表单导出PDF到本地,然后再打开打印,而他的工作有很大一部分是需要打印表单的,所以为了减轻工作量,向我提了这份需求申请,我先考虑了一下这个需求的实现难度,感觉应该是可以实现的,就答应了下来。
为了实现这个功能,我先去网上查找了一下资料,对比了一下实现难度以及我们当前所处的环境(因为我们公司文件落地加密,所以直接调用打印机打印是不好实现的),我最终选择使用 Apache PDFbox 这个开源项目来实现 PDF 文件格式的打印。
Apache PDFbox 是一个开源的、基于 Java 的、支持 PDF 文档生成的工具库,它可以用于创建新的 PDF 文档,修改现有的 PDF 文档,还可以从 PDF 文档中提取所需的内容。Apache PDFBox 还包含了数个命令行工具。可以说,这个开源的工具库的功能是很强大的,在此,我们只研究打印功能。
Apache PDFbox 的优点:功能强大,代码开源,较完美的解决了 PDF 格式文件的一系列处理,使用方便。
这里我们获取了本地的所有打印机服务,并将系统的默认打印机作为首选项
//获取本地的打印服务,并且设置默认打印机
private JComboBox<String> selectPrintService() {
defaultPrintService = PrintServiceLookup.lookupDefaultPrintService();//获取默认打印机
JComboBox<String> comboBox = new JComboBox<>();
//获得本台电脑连接的所有打印机
PrintService[] printServices = PrinterJob.lookupPrintServices();
if (printServices == null || printServices.length == 0) {
comboBox.addItem("获取本地打印机失败,请联系管理员!");
} else {
for (PrintService printService : printServices) {
String value = printService.getName();
serviceMap.put(value, printService);//将打印机名称及打印机服务添加到集合
comboBox.addItem(value);
//将默认打印机设置为下拉选的默认选择项
if (defaultPrintService != null && defaultPrintService.getName().equals(value)) {
comboBox.setSelectedItem(value);
}
}
}
return comboBox;
}
//打印功能实现
public void print(Container parent) {
PDDocument document = null;
File file;
try {
file = new File(filepath);
if (file == null || !file.exists()) {
JOptionPane.showMessageDialog(parent, "要打印的PDF文件不存在!", "警告", JOptionPane.WARNING_MESSAGE);
return;
}
document = PDDocument.load(file);
PrinterJob printerJob = PrinterJob.getPrinterJob();
printerJob.setJobName(file.getName());
printerJob.setPrintService(defaultPrintService);//设置打印机
//设置纸张及缩放
PDFPrintable pdfPrintable = new PDFPrintable(document, scaling);
//设置多页打印
Book book = new Book();
PageFormat pageFormat = new PageFormat();
//设置打印方向
pageFormat.setOrientation(orientation);//纵向
Paper paper = getPaper();
pageFormat.setPaper(paper);
book.append(pdfPrintable, pageFormat, document.getNumberOfPages());
printerJob.setPageable(book);
printerJob.setCopies(copies);//设置打印份数
//添加打印属性
HashPrintRequestAttributeSet attributes = new HashPrintRequestAttributeSet();
attributes.add(sides); //设置单双页
attributes.add(MediaSizeName.ISO_A4);//默认A4纸打印
printerJob.print(attributes);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (document != null) {
try {
document.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private Paper getPaper() {
Paper paper = new Paper();
// 默认为A4纸张,对应像素宽和高分别为 595, 842
int width = 595;
int height = 842;
// 设置边距,单位是像素,10mm边距,对应 28px
int marginLeft = 12;
int marginRight = 12;
int marginTop = 12;
int marginBottom = 12;
paper.setSize(width, height);
// 下面一行代码,解决了打印内容为空的问题
paper.setImageableArea(marginLeft, marginRight, width - (marginLeft + marginRight), height - (marginTop + marginBottom));
return paper;
}
运行之后,会弹出类似这个的界面,简单配置下(这里的参数都是默认的,一般用这些就可以直接打印),点击打印,就会将文件传送到打印机服务的打印队列进行打印
我这里选择了 Microsoft Print to PDF 测试一下
这里我们发现使用pdfbox打印出来的PDF的字体发生了改变,打印的结果和原PDF不一致
这里我们去控制台看一下为什么会出现这个问题,在这里我们不难发现导致这个问题的原因在于:原PDF中用到了一种 STSong-Light 的字体,而我们系统没有安装这个字体,所以他就自动给我们换了一个字体,难怪变得这么丑了,这里我们给出两种解决办法
因为程序检测到我们没有安装这种字体,我们只需要将对应字体安装上去,然后重启电脑,应该就能解决这个问题
因为我们不可能给每个用户加装一个字体,所以我们只能从程序出发
我们去交友网站上找到该项目的源码地址:https://github.com/apache/pdfbox/tree/trunk/pdfbox,本来想用源码的,不过可能版本不同,会报错。
所以我们可以在 pdfbox 的 org.apache.pdfbox.pdmodel.font 路径下找到 FontMapperImpl.class 文件,然后把他的反编译代码复制到我们项目中(记得先创建路径文件夹)
然后在字体映射集合中添加所缺字体的映射字体(映射的字体选择系统中有的,且和所缺字体近似的字体)就可以了
//添加STSong-Light字体映射
this.substitutes.put("STSong-Light", Arrays.asList("STSong-Light", "SimSun", "SIMFANG", "STFangsong"));
然后再重试下,就发现可以正常打印了