最近公司做质检的执法项目,牵涉到执法文书的打印。这个功能实现的时候走了不少弯路,简单记录下,以备后用。
甲方的要求比较苛刻:1、打印功能不依赖于客户pc机上的word程序 2、打印功能不依赖于特定的浏览器插件 3、不依赖于其他商业程序。 这样,我只能通过调用IE内置的IEWebBrowser控件进行打印。大部分文书可以调整显示样式,来达到标准文书的要求,而一些比较特殊的文书如《现场检查笔录》,由于嵌套富文本,前台很难实现标准文书的打印效果。这类特殊文书的打印,我的思路是服务器生成打印图片,前台调用打印组件打印。
打印图片的生成主要分为3步:1、根据word模板填充数据,生成新的word文件 2、word转pdf 3、pdf转图片
生成word主要有两种方法:1、poi生成word(本例中使用的方法) 2、使用freemarker生成word
照例先copy下poi项目介绍:POI项目的使命是创建和维护Java api操纵各种文件格式 基于Office Open XML标准(OOXML)和微软的OLE 2复合文档格式(OLE2)。 简而言之,您可以使用Java读写MS Excel文件。其中,HWPF提供对word97的支持,XWPF提供对word2007(ooxml国际标准)的支持。
首先,参照官方代码写了测试方法:
文本替换测试:
@Test public void testPoi() { try { //docx文件的文档对象 XWPFDocument xwpfDocument=new XWPFDocument(POIXMLDocument.openPackage("D:/testPoi.docx")); //XWPFParagraph 包含在文档/表格/标题中的段落(段落中包含很多样式信息) //需求:替换段落中的文本/图片等,不涉及新加段落 //遍历文档的段落对象(不包括页眉页脚) for (XWPFParagraph xwpfParagraph : xwpfDocument.getParagraphs()) { //XWPFRun对象定义了文本区域的一组公共的属性 for (XWPFRun xwpfRun : xwpfParagraph.getRuns()) { //文本替换 if("${营业执照}".equalsIgnoreCase(xwpfRun.getText(xwpfRun.getTextPosition()))){ //{1} xwpfRun.setColor("FF0000");//设置文本颜色 xwpfRun.setText("我的营业执照",0);//文本替换 //xwpfRun.setText("我的营业执照1",-1); //在当前文本后追加文本 //xwpfRun.setText("我的营业执照12",2); //在当前文本后追加文本 //xwpfRun.setText("我的营业执照11",1); //在“我的营业执照1”后追加文本 } //图片替换添加 if("${二维码}".equalsIgnoreCase(xwpfRun.getText(xwpfRun.getTextPosition()))){ //{2} xwpfRun.setText("",0);//文本替换 //在文档中插入图片失败 //xwpfParagraph.createRun().addPicture(new FileInputStream(new File("D:/二维码.PNG")), Document.PICTURE_TYPE_PNG, "二维码", Units.toEMU(200), Units.toEMU(200)); //xwpfRun.addPicture(new FileInputStream(new File("D:/二维码.PNG")), Document.PICTURE_TYPE_PNG, "二维码", Units.toEMU(200), Units.toEMU(200)); //create run需要结束当前循环 //break; } } } //文档create 添加图片失败 //xwpfDocument.createParagraph().createRun().addPicture(new FileInputStream(new File("D:/二维码.PNG")), Document.PICTURE_TYPE_PNG, "二维码", Units.toEMU(200), Units.toEMU(200)); FileOutputStream fos = new FileOutputStream(new File("D:/testPoi1.docx")); xwpfDocument.write(fos); fos.flush(); fos.close(); } catch (IOException e) { System.out.println("加载文件失败"); e.printStackTrace(); } catch (Exception e) { System.out.println("序列化图片失败"); e.printStackTrace(); } }
比较坑的是word中写的标记会被ms解析成不同的run,需要自行修改:
应为:
Poi添加图片的方法存在bug,官方暂时还没有修复:
执行添加图片方法后,打不开文档:
文档插入图片bug修复并测试:
新建类:CustomXWPFDocument继承XWPFDocument
添加方法:createPic
public void createPic(String blipId,int id, int width, int height,CTInline inline) { final int EMU = 9525; width *= EMU; height *= EMU; //String blipId = getAllPictures().get(id).getPackageRelationship().getId(); //CTInline inline = createParagraph().createRun().getCTR().addNewDrawing().addNewInline(); String picXml = "" + "<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">" + " <a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">" + " <pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">" + " <pic:nvPicPr>" + " <pic:cNvPr id=\"" + id + "\" name=\"Generated\"/>" + " <pic:cNvPicPr/>" + " </pic:nvPicPr>" + " <pic:blipFill>" + " <a:blip r:embed=\"" + blipId + "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"/>" + " <a:stretch>" + " <a:fillRect/>" + " </a:stretch>" + " </pic:blipFill>" + " <pic:spPr>" + " <a:xfrm>" + " <a:off x=\"0\" y=\"0\"/>" + " <a:ext cx=\"" + width + "\" cy=\"" + height + "\"/>" + " </a:xfrm>" + " <a:prstGeom prst=\"rect\">" + " <a:avLst/>" + " </a:prstGeom>" + " </pic:spPr>" + " </pic:pic>" + " </a:graphicData>" + "</a:graphic>"; //CTGraphicalObjectData graphicData = inline.addNewGraphic().addNewGraphicData(); XmlToken xmlToken = null; try { xmlToken = XmlToken.Factory.parse(picXml); } catch(XmlException xe) { xe.printStackTrace(); } inline.set(xmlToken); //graphicData.set(xmlToken); inline.setDistT(0); inline.setDistB(0); inline.setDistL(0); inline.setDistR(0); CTPositiveSize2D extent = inline.addNewExtent(); extent.setCx(width); extent.setCy(height); CTNonVisualDrawingProps docPr = inline.addNewDocPr(); docPr.setId(id); docPr.setName("Picture " + id); docPr.setDescr("Generated"); }
@Test public void testPoi1() { try { //docx文件的文档对象 CustomXWPFDocument xwpfDocument=new CustomXWPFDocument(POIXMLDocument.openPackage("D:/testPoi.docx")); //遍历页眉页脚 for (XWPFHeaderFooter xwpfhf : xwpfDocument.getHeaderList()) { //这部分可以作为方法提取出来 for (XWPFParagraph xwpfParagraph : xwpfhf.getParagraphs()) { for (XWPFRun xwpfRun : xwpfParagraph.getRuns()) { //图片替换添加 在页眉上添加图片没有实现 if("${二维码}".equalsIgnoreCase(xwpfRun.getText(xwpfRun.getTextPosition()))){ //{2} //添加图片前,设置段落行角色为 自动 xwpfParagraph.setSpacingLineRule(LineSpacingRule.AUTO); CTInline ctinline=xwpfRun.getCTR().addNewDrawing().addNewInline(); String id = xwpfDocument.addPictureData(new FileInputStream(new File("D:\\erweima.jpg")), Document.PICTURE_TYPE_JPEG); int id2=xwpfDocument.getAllPackagePictures().size()+111; xwpfDocument.createPic(id,id2, 259, 259,ctinline); } } } } //XWPFParagraph 包含在文档/表格/标题中的段落(段落中包含很多样式信息) //需求:替换段落中的文本/图片等,不涉及新加段落 //遍历文档的段落对象(不包括页眉页脚) for (XWPFParagraph xwpfParagraph : xwpfDocument.getParagraphs()) { //XWPFRun对象定义了文本区域的一组公共的属性 for (XWPFRun xwpfRun : xwpfParagraph.getRuns()) { //文本替换 if("${营业执照}".equalsIgnoreCase(xwpfRun.getText(xwpfRun.getTextPosition()))){ //{1} xwpfRun.setColor("FF0000");//设置文本颜色 xwpfRun.setText("我的营业执照",0);//文本替换 //xwpfRun.setText("我的营业执照1",-1); //在当前文本后追加文本 //xwpfRun.setText("我的营业执照12",2); //在当前文本后追加文本 //xwpfRun.setText("我的营业执照11",1); //在“我的营业执照1”后追加文本 } //图片替换添加 if("${二维码}".equalsIgnoreCase(xwpfRun.getText(xwpfRun.getTextPosition()))){ //{2} //添加图片前,设置段落行角色为 自动 xwpfParagraph.setSpacingLineRule(LineSpacingRule.AUTO); CTInline ctinline=xwpfRun.getCTR().addNewDrawing().addNewInline(); String id = xwpfDocument.addPictureData(new FileInputStream(new File("D:\\二维码.PNG")), Document.PICTURE_TYPE_JPEG); int id2=xwpfDocument.getAllPackagePictures().size()+1; xwpfDocument.createPic(id,id2, 259, 259,ctinline); } } } //文档create 添加图片失败 //xwpfDocument.createParagraph().createRun().addPicture(new FileInputStream(new File("D:/二维码.PNG")), Document.PICTURE_TYPE_PNG, "二维码", Units.toEMU(200), Units.toEMU(200)); FileOutputStream fos = new FileOutputStream(new File("D:/testPoi1.docx")); xwpfDocument.write(fos); fos.flush(); fos.close(); } catch (IOException e) { System.out.println("加载文件失败"); e.printStackTrace(); } catch (Exception e) { System.out.println("序列化图片失败"); e.printStackTrace(); } }
图片添加经常出现如图的效果:
这是图片空间不足引起的,在document.xml中查看是:
<w:spacing w:line="500" w:lineRule="exact"/>
所以设计模板时或者在代码中修改为 w:lineRule='auto'即可
测试代码和word文件:http://yunpan.cn/cfKAtVfakJQpN (提取码:31a0)
注:页眉页脚中添加/替换图片都没有实现,求探讨指教
注:4/5年没写过文章了,特别生疏,勿怪
注:做的时候出现不少错误,待添加
另外,freemarker的实现会放在下一篇中。