【WEB打印实用】分享一个关于非标打印的实践案例
-
先说结论
我们经过三个方案的调试,最后选择了方案三做为标定方案,才达到客户满意的效果。后续也推荐对于非标打印,按照方案三执行。
祝大家少走弯路~
需求
客户想实现80mm * 60mm / 100mm * 80mm的非标打印。
尝试过的方案
方案一
使用后端word模板渲染出最终的word数据,将word输出给前端,前端通过docx-preview预览word,然后使用print-js来选取DOM进行打印。
效果
实现了单张对应尺寸的打印。缺陷
单张打印的效果好,但是对于批量打印如50张同时打印,就会出现表格跨页问题,具体见下图。

原因分析
word渲染表格,表格的高度没办法做精确定位,即便定位的很准,但是丝毫误差在数量多的情况下,就会出现误差放大的情况。方案二
直接用前端表格组件库,如table来渲染表格,然后通过print-js来选取DOM进行打印,打印尺寸用css来控制。
效果
实现了单张对应尺寸的打印。缺陷
- 出现了跨页打印表格的问题(但是精确度比方案一好了很多)
- 出现了打印内容丢失的问题
见下图

原因分析
跨页问题基本是一致的,失之毫厘,差之千里。
那么为什么会出现打印内容的丢失呢?因为使用纯前端进行Table绘制,Table的大小一方面受基础的css控制,另外一方面,还会受单元格的文字大小、文字长度控制,如果文字过长,单元格会自适应加长,表格会被膨胀,导致尺寸超过了预定的尺寸,因而打印内容丢失。(有人问:为什么不控制单元格内容溢出隐藏?因为当前方案存在方案一的缺陷,所以再努力也是不满足需求的,所以没有继续调下去)方案三(最终方案)
后端采用itextpdf直接渲染pdf,然后通过流式下载到浏览器,高版本的浏览器会自动识别PDF文件进行主动预览,预览效果好不说,还能直接调用打印机进行打印。
重点是因为itextpdf的每一页内容都是通过document.newPage()方法来手动控制的,这样就可以非常有效的控制每个页面的大小和尺寸,不像word和html在调度打印机的时候,分页是靠整个文章的高度然后除以每页的高度计算出来的。效果
完美解决跨页表格问题,完美支持百张批量打印,主要是客户满意!缺陷
- 复杂的表格内容,itextpdf绘制可能不太方便
- itextpdf需要引入中文字体库,否则中文字体无法渲染(坑点)
样例代码展示
/** * 生成pdfone * * @param outputStream 输出流 * @param widthMM 宽度 * @param heightMM 高度mm * @param productionOrderCardPrintResults 生产订单卡片打印结果 */ private void generatePDFOne(ServletOutputStream outputStream, Float widthMM, Float heightMM, List<ProductionOrderCardPrintResult> productionOrderCardPrintResults) { // 定义列宽(毫米) float width = widthMM * MM_TO_POINT; float height = heightMM * MM_TO_POINT; float widthItem = width / 76; float heightItem = height / 59; // 缩放比例(保持宽高等比) float scale = widthMM / BASE_WIDTH_MM; float[] columnWidths = { widthItem * 10, // A列 widthItem * 25, // B列 widthItem * 10, // C列 widthItem * 25 // D列 }; // 定义行高(毫米) float normalHeight = heightItem * 7; float row3Height = heightItem * 12; // 二维码大小 float qrImageWidth = Math.min(widthItem * 35, normalHeight * 4); // 创建自定义页面大小 Rectangle pageSize = new Rectangle(width, height); // 创建文档边距 Document document = new Document(pageSize, widthItem, 0, heightItem, 0); try { PdfWriter.getInstance(document, outputStream); document.open(); BaseFont baseFont = loadFont(); Font font = new Font(baseFont, 11 * scale, Font.BOLD); for (int i = 0; i < productionOrderCardPrintResults.size(); i++) { if (i > 0) { // 这里手动新增一个新页,从而保证打印的尺寸是精确的 document.newPage(); } ProductionOrderCardPrintResult productionOrderCardPrintResult = productionOrderCardPrintResults.get(i); String workOrderNo = productionOrderCardPrintResult.getWorkOrderNo(); String actualOrderNo = productionOrderCardPrintResult.getActualOrderNo(); String materialName = productionOrderCardPrintResult.getMaterialName(); String materialNumber = productionOrderCardPrintResult.getMaterialNumber(); String specification = productionOrderCardPrintResult.getSpecification(); String number = productionOrderCardPrintResult.getNumber(); String deliveryTime = productionOrderCardPrintResult.getDeliveryTime(); String customerName = productionOrderCardPrintResult.getCustomerName(); String remark = productionOrderCardPrintResult.getRemark(); Integer printCount = productionOrderCardPrintResult.getPrintCount(); String workOrderNoFormat = StrUtil.blankToDefault(workOrderNo, DEFAULT_STR); String actualOrderNoFormat = StrUtil.blankToDefault(actualOrderNo, DEFAULT_STR); String materialNumberFormat = StrUtil.blankToDefault(materialNumber, DEFAULT_STR); String numberFormat = StrUtil.blankToDefault(number, DEFAULT_STR); String deliveryTimeFormat = StrUtil.blankToDefault(deliveryTime, DEFAULT_STR); String customerNameFormat = StrUtil.blankToDefault(customerName, DEFAULT_STR); String specificationFormat = StrUtil.blankToDefault(specification, DEFAULT_STR); String remarkFormat = StrUtil.blankToDefault(remark, DEFAULT_STR); // 创建表格 PdfPTable table = new PdfPTable(4); table.setTotalWidth(columnWidths); table.setLockedWidth(true); addCell(table, "工单", font, normalHeight, 1, 1); addCell(table, workOrderNoFormat, font, normalHeight, 1, 3); addCell(table, "物料", font, normalHeight, 1, 1); addCell(table, materialNumberFormat, font, normalHeight, 1, 1); addCell(table, "订单", font, normalHeight, 1, 1); addCell(table, actualOrderNoFormat, font, normalHeight, 1, 1); addCell(table, "描述", font, row3Height, 1, 1); addCell(table, specificationFormat, font, row3Height, 1, 3); addCell(table, "数量", font, normalHeight, 1, 1); addCell(table, numberFormat, font, normalHeight, 1, 1); // C4-D7合并单元格,放置二维码 PdfPCell qrCell = new PdfPCell(); qrCell.setRowspan(4); qrCell.setColspan(2); qrCell.setFixedHeight(normalHeight * 4); qrCell.setHorizontalAlignment(Element.ALIGN_CENTER); qrCell.setVerticalAlignment(Element.ALIGN_CENTER); // 生成二维码 JSONObject jsonObject = new JSONObject(); jsonObject.put("workOrderNo", workOrderNo); Image qrImage = createQrCodeImage(jsonObject.toJSONString(), qrImageWidth); qrImage.scaleToFit(qrImageWidth * 0.9f, qrImageWidth * 0.9f); qrCell.addElement(qrImage); table.addCell(qrCell); // 第5行 addCell(table, "交期", font, normalHeight, 1, 1); addCell(table, deliveryTimeFormat, font, normalHeight, 1, 1); // 第6行 addCell(table, "客户", font, normalHeight, 1, 1); addCell(table, customerNameFormat, font, normalHeight, 1, 1); // 第7行 addCell(table, "备注", font, normalHeight, 1, 1); addCell(table, remarkFormat, font, normalHeight, 1, 1); document.add(table); } } catch (Exception e) { log.error("生成PDF失败", e); } finally { document.close(); } } // 使用 Hutool 生成二维码(无白边) private Image createQrCodeImage(String content, float qrSize) throws Exception { QrConfig config = new QrConfig((int) qrSize, (int) qrSize); // 去掉白边 config.setMargin(0); config.setErrorCorrection(com.google.zxing.qrcode.decoder.ErrorCorrectionLevel.M); // 黑色 config.setForeColor(Color.BLACK); // 白底 config.setBackColor(Color.WHITE); // 生成 BufferedImage java.awt.image.BufferedImage qrImage = QrCodeUtil.generate(content, config); // 转成 iText Image ByteArrayOutputStream baos = new ByteArrayOutputStream(); javax.imageio.ImageIO.write(qrImage, "png", baos); return Image.getInstance(baos.toByteArray()); } /** * 添加单元格 */ private static void addCell(PdfPTable table, String text, Font font, float height, int rowspan, int colspan) { PdfPCell cell = new PdfPCell(new Phrase(text, font)); cell.setFixedHeight(height); cell.setRowspan(rowspan); cell.setColspan(colspan); cell.setVerticalAlignment(Element.ALIGN_MIDDLE); cell.setHorizontalAlignment(Element.ALIGN_LEFT); table.addCell(cell); } -
@温暖的火龙果687 感谢提醒!另补充,可以使用Apache PDFBox替代iText库,本次分享只是提供一个参考方案。