最近在项目里需要对简历进行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>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>html2pdf</artifactId>
<version>1.0.1</version>
</dependency>
<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();
writerProperties.addXmpMetadata();
PdfWriter pdfWriter = new PdfWriter(response.getOutputStream(), writerProperties);
PdfDocument pdfDoc = new PdfDocument(pdfWriter);
pdfDoc.getCatalog().setLang(new PdfString("UTF-8"));
pdfDoc.setTagged();
pdfDoc.getCatalog().setViewerPreferences(new PdfViewerPreferences().setDisplayDocTitle(true));
PdfDocumentInfo pdfMetaData = pdfDoc.getDocumentInfo();
pdfMetaData.setAuthor("XX");
pdfMetaData.addCreationDate();
pdfMetaData.getProducer();
pdfMetaData.setCreator("XXX");
pdfMetaData.setKeywords("resume");
pdfMetaData.setSubject("PDF resume");
String footer = "来自:XX网 - www.XXX.com";
Footer footerHandler = new Footer(footer,msyh);
pdfDoc.addEventHandler(PdfDocumentEvent.END_PAGE,footerHandler);
ConverterProperties props = new ConverterProperties();
FontProvider fp = new FontProvider();
fp.addStandardPdfFonts();
fp.addDirectory(resources);
props.setFontProvider(fp);
props.setBaseUri(resources);
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();
protected class Header implements IEventHandler {
String header;
public Header(String header) {
this.header = header;
@Override
public void handleEvent(Event event) {
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);
canvas.showTextAligned(header,
pageSize.getWidth() / 2,
pageSize.getTop() - 30, TextAlignment.CENTER);
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) {
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);
canvas.showTextAligned(footer,
pageSize.getWidth() / 2,
pageSize.getBottom() + 30, TextAlignment.CENTER);
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/");
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);
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 conversion
ConverterProperties 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代码是这样的
<li>测试1<br>测试2</li>
生成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资料是在太少,特别是中文资料,这次能解决也算是缘分吧
注意两个依赖的版本对应,进入html2pdf的pom文件就能看到itext的版本,font-asian的版本最低也要是html2pdf中itext7的版本。
公司最近有做做Java导出pdf的需求,因为之前没有做过的缘故,所以从网上找来了itextpdf的包,前期做的还是非常顺利的,本地测试都是非常的顺利,正当我以为导出pdf如此简单的时候,上Linux测试就踩了大坑。
<!-- 字符串转pdf -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId&g...
在利用itext将html文件转化为pdf文件的过程中,需要适当的调整字体,下面介绍字体的使用样式:字体支持的样式 itext核心库中主要支持一下几个各种的字体文件:.AFM/.PFB/.TTF/.otf/.ttc/.wof 2. 利用DefaultFontProvider 只是支持 14 Standard Type 1 fonts and 12 fonts 主要字体...
springboot项目实现html转pdf功能,踩过一些坑,记录遇到的问题。 附java代码,html代码,字体库下载地址,可直接运行main方法,生成pdf。
iText 7是一个Java库,它提供了一些用于生成PDF文件的API。iText 7也支持从HTML文件生成PDF文件。下面是一个简单的示例,用于将HTML转换为PDF:
import com.itextpdf.html2pdf.HtmlConverter;
import java.io.File;
import java.io.FileInputStream;
import java.io.F...
在第4章中,我们将仔细研究这个用例,使用XSLT在内存中将XML转换为HTML,然后使用pdfHTML插件将HTML转换为PDF。2016年,我们发布了iText 7,这是iText的一个全新版本,不再与以前的版本兼容,但它是在考虑pdfHTML的情况下创建的。在iText 7中,您有一个表和单元格对象,当您为整个表设置不同的字体时,该字体将继承为每个单元格的默认字体。如果是这种情况,您要么使用了过时的HTMLWorker类(iText 2),要么使用了旧的XML Worker插件(iText 5)。
这时,大家会发现中文丢失,这是itextpdf 工具html转pdf 的第一个通用问题。这个问题很好解决,引入字体设置就可以了。当然这样设置背景还有一个弊端,就是pdf有多页,这个只显示一页。简单的模板样式都可以这样转换,但是如果有复杂的html页面,这样转换你会发现,好多html标签及css样式不生效。3 如果像设置背景图片,在html 设置是不在pdf转换过程中生效的,可以换种方式。添加字体(记得添加ttf的,ttc的需要设置什么,不然不会识别)1注意标签一定要闭合,不然。