iText 7 html2pdf 使用总结
来源:互联网 发布:淘宝密码对的登不上 编辑:程序博客网 时间:2024/06/05 13:36
最近在项目里需要对简历进行pdf导出,之前试用了一下iText 5,功能倒是没什么问题,但是XMLWorkerHelper对于CSS的支持程度是在是让我蛋疼,生成的pdf内容排版各种问题,遂不得不放弃iText 5,后来上iText官网发现有最新版iText 7.0.4,而且试用了官网上的示例程序,对css的支持也还可以,所以决定就用它了。
1.依赖jar包
<dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.26-incubating</version></dependency><dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId></dependency><dependency> <groupId>com.itextpdf</groupId> <artifactId>itext7-core</artifactId> <version>7.0.4</version></dependency><!-- pdfHTML --><dependency> <groupId>com.itextpdf</groupId> <artifactId>html2pdf</artifactId> <version>1.0.1</version></dependency><!-- iText 7 License Key Library --><dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-licensekey</artifactId> <version>2.0.4</version></dependency>
由于html2pdf和itext-licensekey不是开源的,所以这两个包在maven中央仓库没有,这里需要添加iText maven仓库
<repositories> <repository> <id>itext</id> <name>iText Repository - releases</name> <url>https://repo.itextsupport.com/releases</url> </repository></repositories>
具体怎么添加请自行百度
2.功能开发
jar包下好了,我们就可以进行开发了,由于项目中使用的是freemarker作为前台模板,所以需要将ftl模板和数据生成html字符串
freemarker工具类
/** * Created by lc on 2017/8/23 * FREEMARKER 模板工具类 * */public class FreeMarkerUtil { /** * @description 获取模板 */ public static String getContent(String fileName,Object data){ String templatePath=getPDFTemplatePath(fileName).replace("\\","/"); String templateFileName=getTemplateName(templatePath); String templateFilePath=getTemplatePath(templatePath); if(StringUtils.isEmpty(templatePath)){ throw new FreeMarkerException("templatePath can not be empty!"); } try{ Configuration config = new Configuration(Configuration.VERSION_2_3_25); config.setDefaultEncoding("UTF-8"); config.setDirectoryForTemplateLoading(new File(templateFilePath)); config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); config.setLogTemplateExceptions(false); Template template = config.getTemplate(templateFileName); StringWriter writer = new StringWriter(); template.process(data, writer); writer.flush(); String html = writer.toString(); return html; }catch (Exception ex){ throw new FreeMarkerException("FreeMarkerUtil process fail",ex); } } private static String getTemplatePath(String templatePath) { if(StringUtils.isEmpty(templatePath)){ return ""; } String path=templatePath.substring(0,templatePath.lastIndexOf("/")); return path; } private static String getTemplateName(String templatePath) { if(StringUtils.isEmpty(templatePath)){ return ""; } String fileName=templatePath.substring(templatePath.lastIndexOf("/")+1); return fileName; } /** * @description 获取PDF的模板路径, * 默认按照PDF文件名匹对应模板 * @param fileName PDF文件名 * @return 匹配到的模板名 */ public static String getPDFTemplatePath(String fileName){ String classpath=CreatePDF.class.getClassLoader().getResource("").getPath(); String templatePath=classpath+"pdfHtml/resumePDF"; File file=new File(templatePath); if(!file.isDirectory()){ throw new PDFException("PDF模板文件不存在,请检查pdfHtml文件夹!"); } String pdfFileName=fileName.substring(0,fileName.lastIndexOf(".")); File defaultTemplate=null; File matchTemplate=null; for(File f:file.listFiles()){ if(!f.isFile()){ continue; } String templateName=f.getName(); if(templateName.lastIndexOf(".ftl")==-1){ continue; } if(defaultTemplate==null){ defaultTemplate=f; } if(StringUtils.isEmpty(fileName)&&defaultTemplate!=null){ break; } templateName=templateName.substring(0,templateName.lastIndexOf(".")); if(templateName.toLowerCase().equals(pdfFileName.toLowerCase())){ matchTemplate=f; break; } } if(matchTemplate!=null){ return matchTemplate.getAbsolutePath(); } if(defaultTemplate!=null){ return defaultTemplate.getAbsolutePath(); } return null; }}
自定义异常类
public class FreeMarkerException extends BaseException { public FreeMarkerException(){ super("FreeMarker异常"); } public FreeMarkerException(int errorCode, String errorMsg){ super(errorMsg); this.errorCode=errorCode; this.errorMsg=errorMsg; } public FreeMarkerException(String errorMsg){ super(errorMsg); this.errorCode=500; this.errorMsg=errorMsg; } public FreeMarkerException(String errorMsg, Exception e){ super(errorMsg,e); this.errorCode=500; this.errorMsg=errorMsg; }}
public class PDFException extends BaseException { public PDFException(){ super("PDF异常"); } public PDFException(int errorCode, String errorMsg){ super(errorMsg); this.errorCode=errorCode; this.errorMsg=errorMsg; } public PDFException(String errorMsg){ super(errorMsg); this.errorCode=500; this.errorMsg=errorMsg; } public PDFException(String errorMsg, Exception e){ super(errorMsg,e); this.errorCode=500; this.errorMsg=errorMsg; }}
重点来了,CreatePDF类
public class CreatePDF { public static final String FONT = CreatePDF.class.getClassLoader().getResource("").getPath()+"pdfHTML/resumePDF/msyh.ttf"; public void createPdf(String src, String resources, Object object,HttpServletResponse response) throws IOException { try { PdfFont msyh = PdfFontFactory.createFont(FONT, PdfEncodings.IDENTITY_H); WriterProperties writerProperties = new WriterProperties(); //Add metadata writerProperties.addXmpMetadata(); PdfWriter pdfWriter = new PdfWriter(response.getOutputStream(), writerProperties); PdfDocument pdfDoc = new PdfDocument(pdfWriter); pdfDoc.getCatalog().setLang(new PdfString("UTF-8")); //Set the document to be tagged pdfDoc.setTagged(); pdfDoc.getCatalog().setViewerPreferences(new PdfViewerPreferences().setDisplayDocTitle(true)); //Set meta tags PdfDocumentInfo pdfMetaData = pdfDoc.getDocumentInfo(); pdfMetaData.setAuthor("XX"); pdfMetaData.addCreationDate(); pdfMetaData.getProducer(); pdfMetaData.setCreator("XXX"); pdfMetaData.setKeywords("resume"); pdfMetaData.setSubject("PDF resume"); //Title is derived from html //Create event-handlers String footer = "来自:XX网 - www.XXX.com"; Footer footerHandler = new Footer(footer,msyh);// PageXofY footerHandler = new PageXofY(pdfDoc); //Assign event-handlers pdfDoc.addEventHandler(PdfDocumentEvent.END_PAGE,footerHandler); // pdf conversion ConverterProperties props = new ConverterProperties(); FontProvider fp = new FontProvider(); fp.addStandardPdfFonts(); fp.addDirectory(resources);//The noto-nashk font file (.ttf extension) is placed in the resources props.setFontProvider(fp); props.setBaseUri(resources); //Setup custom tagworker factory for better tagging of headers //DefaultTagWorkerFactory tagWorkerFactory = new TagWorkerFactory(); //props.setTagWorkerFactory(tagWorkerFactory); HtmlConverter.convertToPdf(new ByteArrayInputStream(FreeMarkerUtil.getContent(src,object).getBytes("UTF-8")), pdfDoc, props); pdfDoc.close(); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } //Header event handler protected class Header implements IEventHandler { String header; public Header(String header) { this.header = header; } @Override public void handleEvent(Event event) { //Retrieve document and PdfDocumentEvent docEvent = (PdfDocumentEvent) event; PdfDocument pdf = docEvent.getDocument(); PdfPage page = docEvent.getPage(); Rectangle pageSize = page.getPageSize(); PdfCanvas pdfCanvas = new PdfCanvas( page.getLastContentStream(), page.getResources(), pdf); Canvas canvas = new Canvas(pdfCanvas, pdf, pageSize); canvas.setFontSize(18f); //Write text at position canvas.showTextAligned(header, pageSize.getWidth() / 2, pageSize.getTop() - 30, TextAlignment.CENTER); } } //Header event handler protected class Footer implements IEventHandler { String footer; PdfFont pdfFont; public Footer(String footer,PdfFont pdfFont) { this.footer = footer; this.pdfFont = pdfFont; } @Override public void handleEvent(Event event) { //Retrieve document and PdfDocumentEvent docEvent = (PdfDocumentEvent) event; PdfDocument pdf = docEvent.getDocument(); PdfPage page = docEvent.getPage(); Rectangle pageSize = page.getPageSize(); PdfCanvas pdfCanvas = new PdfCanvas( page.getLastContentStream(), page.getResources(), pdf); Canvas canvas = new Canvas(pdfCanvas, pdf, pageSize); canvas.setFontSize(8f); canvas.setFont(pdfFont); //Write text at position canvas.showTextAligned(footer, pageSize.getWidth() / 2, pageSize.getBottom() + 30, TextAlignment.CENTER); } } //page X of Y protected class PageXofY implements IEventHandler { protected PdfFormXObject placeholder; protected float side = 20; protected float x = 300; protected float y = 25; protected float space = 4.5f; protected float descent = 3; public PageXofY(PdfDocument pdf) { placeholder = new PdfFormXObject(new Rectangle(0, 0, side, side)); } @Override public void handleEvent(Event event) { PdfDocumentEvent docEvent = (PdfDocumentEvent) event; PdfDocument pdf = docEvent.getDocument(); PdfPage page = docEvent.getPage(); int pageNumber = pdf.getPageNumber(page); Rectangle pageSize = page.getPageSize(); PdfCanvas pdfCanvas = new PdfCanvas( page.getLastContentStream(), page.getResources(), pdf); Canvas canvas = new Canvas(pdfCanvas, pdf, pageSize); Paragraph p = new Paragraph() .add("Page ").add(String.valueOf(pageNumber)).add(" of"); canvas.showTextAligned(p, x, y, TextAlignment.RIGHT); pdfCanvas.addXObject(placeholder, x + space, y - descent); pdfCanvas.release(); } public void writeTotal(PdfDocument pdf) { Canvas canvas = new Canvas(placeholder, pdf); canvas.showTextAligned(String.valueOf(pdf.getNumberOfPages()), 0, descent, TextAlignment.LEFT); } }}
当然,我这里是直接获取response的outputstream,你也可以自己创建输出流下载到本地
然后是业务调用代码
public static final String sourceFolder = FilePathUtil.getRealFilePath(CreatePDF.class.getClassLoader().getResource("").getPath()+"pdfHTML/resumePDF/"); //License key path public static final String LICENSE = CreatePDF.class.getClassLoader().getResource("").getPath()+"pdfHTML/itextkey-0.xml";@RequestMapping("downloadPDF") public void downloadPDF(Long id,HttpServletResponse response){ try{ //...这里是业务调用逻辑 Map<String, Object> map=new HashMap<>(); map.put("demo","demo"); String fileName = "demo.pdf"; response.setContentType("application/force-download");// 设置强制下载不打开 response.setHeader("Content-Disposition", "attachment; filename="+fileName); LicenseKey.loadLicenseFile(LICENSE);//加载iText License String htmlSource = "resume.ftl"; String resourceFolder = sourceFolder; new CreatePDF().createPdf(htmlSource,resourceFolder,map,response); }catch (Exception e){ e.printStackTrace(); } }
以上就是实现将ftl模板载入数据后拼装成的html生成pdf
3.使用问题
下面说说遇到的几个问题
1)字体问题
在咱这儿使用iText最常见的问题就是中文字体问题了,在iText5里面处理起来还挺麻烦,但是到了iText7,字体问题变得很好解决。
使用字体分以下两种情况
①在html中使用字体
// pdf conversionConverterProperties props = new ConverterProperties();FontProvider fp = new FontProvider();fp.addStandardPdfFonts();fp.addDirectory(resources);props.setFontProvider(fp);props.setBaseUri(resources);
这里要注意,BaseUri是你存放生成pdf需要使用的资源文件的基础路径,比如css、字体和图片,而这个路径,在windows环境下绝对不能有前面的斜杠,因为
class.getClassLoader().getResource(“”).getPath()
获取到的路径默认前面是带斜杠的,比如/E:/test,而BaseUri只支持E:/test
②在页眉、页脚或自定义元素中使用字体
首先定义字体
PdfFont msyh = PdfFontFactory.createFont("font/msyh.ttf", PdfEncodings.IDENTITY_H);
然后很多地方都可以直接setFont
PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf);Canvas canvas = new Canvas(pdfCanvas, pdf, pageSize);canvas.setFontSize(8f);canvas.setFont(msyh);
2)base64图片识别问题
这个问题在iText5中需要单独处理,但是在iText7中不需要处理,这也是我找了半天实在没有解决办法的情况下直接把base64丢进去之后才发现的~~
3)css问题
因dead line所限,我没有做更多的测试,目前就我发现无法使用的css有以下2种:
overflow、border-radio
如果是想要将image调整为圆形的话,可以使用以下方法:
①做一个内容为圆形,剩余部分透明的图片~~
②通过剪裁,将图片切成圆形,代码:
ImageData img = ImageDataFactory.create(imgSrc);Image imgModel = new Image(img);float w = imgModel.getImageScaledWidth();float h = imgModel.getImageScaledHeight();PdfFormXObject xObject = new PdfFormXObject(new Rectangle(850, 600));PdfCanvas xObjectCanvas = new PdfCanvas(xObject, pdfDoc);xObjectCanvas.ellipse(0, 0, 850, 600, 5);xObjectCanvas.clip();xObjectCanvas.newPath();xObjectCanvas.addImage(img, w, 0, 0, h, 0, -600);com.itextpdf.layout.element.Image clipped = new com.itextpdf.layout.element.Image(xObject);clipped.scale(0.5f, 0.5f);
原文详见
http://developers.itextpdf.com/content/best-itext-questions-stackoverview/image-examples/itext7-how-give-image-rounded-corners
这需要将html转换为element列表,然后再对相应的element进行操作,然后再用List<IElement>
生成pdf。
还是dead line的原因,我没有使用这个方法,因为我没找到该怎么用List<IElement>
生成pdf,我直接把这部分图片给去掉了。
4)html标签
我有一段html代码是这样的
<ul> <li>测试1<br>测试2</li></ul>
生成pdf时报错
ERROR com.itextpdf.html2pdf.attach.impl.DefaultHtmlProcessor - Worker of type com.itextpdf.html2pdf.attach.impl.tags.LiTagWorker unable to process com.itextpdf.html2pdf.attach.impl.tags.BrTagWorker
虽然会报这样的错误,但是pdf有的时候还是能生成的,这个问题困扰了我将近1个小时,各种google也找不到这个报错的相关信息,然后突然间就我想了一下,为什么我要把它当成英文报错,而不是中文报错。
字面意思,就是LiTagWorker不能处理BrTagWorker,对于tagWorker的处理机制我不太了解,所以从我的理解来看,就是<br>
标签不能放在<li>
标签中。
这简单~把html改改就是了~
这个问题引发我的反思,像我这种英文不好的开发人员,遇到问题的第一反应不应该是google,而是先把错误信息翻译出来再说
tips:
html2pdf 是属于收费功能,如果要使用html2pdf,需要在iText官网联系客服购买license(话说我昨天发的邮件这会儿还没人回我,看来我注定只能使用试用版了吗?)
使用license代码
LicenseKey.loadLicenseFile("pdfHTML/itextkey-0.xml");
如果没有这段代码或者你的license无效,生成pdf会报错
com.itextpdf.licensekey.LicenseKeyException: Signature was corrupted.
pdf最终也会下载下来,但是打不开
也有试用版,在iText官网,申请试用
http://pages.itextpdf.com/iText-7-Free-Trial-Landing-Page-1.html
填写信息后会发邮件到你的邮箱,给你一个账户名密码,登陆网站下载试用的license,这个license有30天的试用期。
另外,我试了一下,多个邮箱可以申请多个license重复使用
当然要是有经济能力的话最好还是支持一下购买license
以上就是昨天使用iText7的总结,如果有写的不好的地方请大家指出,我好改正
重点更新:
今天使用功能的时候又发现问题,当纯html长度超出5000的时候,pdf死活导不出来,并且没有报错,然后只能打着断点一步一步的跟,发现执行到中间某一块的时候,代码就停了,而且后续代码并不执行,也没有异常抛出,具体代码位置
com.itextpdf.html2pdf.attach.impl.DefaultHtmlProcessor
代码段
PageBreakApplierUtil.addPageBreakElementBefore(this.context, this.context.getState().top(), element, tagWorker);boolean childProcessed = this.context.getState().top().processTagChild(tagWorker, this.context);PageBreakApplierUtil.addPageBreakElementAfter(this.context, this.context.getState().top(), element, tagWorker);
当功能扫描到某一个html节点树的时候,执行到第二句
this.context.getState().top().processTagChild(tagWorker, this.context);
就停了,一开始我以为还是br标签的问题,结果发现去掉了br标签还是不行,可是各种资料都找不到相关信息,只能靠蒙了
检查css,发现有一个样式信息可以去掉,对整体样式没什么影响
display: inline-block;
去掉后再跑一次,发现果然没问题了,再增加一倍html内容也正常执行。
唉,itext7资料是在太少,特别是中文资料,这次能解决也算是缘分吧
- iText 7 html2pdf 使用总结
- 使用itext转换html2pdf
- [原]itext使用总结
- HTML2PDF
- 使用itext-2.1.7生成word文档总结
- html2pdf中tcpdf使用其他字体
- 使用pd4ml 将html转换为pdf html2pdf
- jxl 和 itext 的 使用总结 (网上查的资料)
- word2html & html2pdf
- 生成pdf,使用itext
- iText使用教程(1)
- iText使用教程(2)
- iText使用教程(3)
- iText使用教程(4)
- iText使用教程(5)
- iText使用教程(6)
- 使用itext生成pdf
- 关于IText的使用
- MySQL练习(一)
- BZOJ1055: [HAOI2008]玩具取名
- Word预防偷窥有绝招
- UVA
- CS与BS的区别
- iText 7 html2pdf 使用总结
- 测试
- 2017HDU多校第9场
- JMeter-配置元件-HTTP Cookie 管理器
- cdh5
- BZOJ1103: [POI2007]大都市meg
- 事务的四个特性
- jquery-尺寸方法
- discuz如何设置上传附件大小及类型