关于Java后台 HTML+CSS3 转换生成PDF文件问题求助!
公司需求:HTML+CSS3(CSS样式是直接写在HTML文件当中)转换为PDF文件,并上传阿里云OSS供用户下载
。
-
本身这个需求并不难,功能做完后不管是PDF质量还是生成速度都还不错,其中也遇到某些CSS样式不支持的问题,都用其他支持的样式替代
-
接下来问题就出现了,文本中突然出现竖行文本CSS样式标签为:writing-mode,生成的PDF中文本并没有竖行显示,明显是itext解析时并不认识这个标签。
-
由于业务逻辑要求,不可采用控制文本区宽度强制文字换行和其他物理方式来实现,目前查了大量资料,所有在HTML转换PDF这个问题上,对CSS都不太友好。
-
后试过用Java调用虚拟打印机、先转word在转pdf、wkhtmltopdf、html2pdf等均没成功
-
总结来说当html中出现竖行文本时,转换PDF希望所见即所得。
-
现在求助有这方面经验的大神能给点解决思路,或者实现方式,小弟在此不胜感激,后面附上源码。
<!-- Itext Html To PDF -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-itext5</artifactId>
<version>9.1.6</version>
</dependency>
<dependency>
<groupId>net.sf.jtidy</groupId>
<artifactId>jtidy</artifactId>
<version>r938</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>2.7.0</version>
</dependency>
@Override
public void createPDF(HttpServletRequest request,String bookId) {
Book book = bookDao.get(bookId);
List<Map<String,Object>> listProvider = bookHtmlDao.getProviderBy(bookId);
bookEditionDao.updateEditionNo(bookId, "1");
String providerName = listProvider.get(0).get("PROVIDER_NAME").toString();
String providerId = listProvider.get(0).get("PROVIDER_ID").toString();
// 初始化目录地址
String ftlPath = request.getSession().getServletContext().getRealPath("/WEB-INF/classes/cn/jointat/mfb/service/toPDF");
String ftlName = "hzSur";
String outputPath = "D:/diskPDF/"+providerId+"_"+providerName+"/"+book.getBookName()+"/"+ftlName;
// 有水印跟无水印版本分文件夹存
String watermarkYesPath = outputPath+"/watermarkYes";
String watermarkNoPath = outputPath+"/watermarkNo";
System.out.println("====>服务商 【"+providerName+"】 请求生成PDF");
System.out.println("========================= PDF开始生成 【"+book.getBookName()+"】 =========================");
// 遍历Html代码块集合
List<BookHtml> listBookHtml = bookHtmlDao.getBookHtml(bookId);
List<String> filesInFolder = new ArrayList<String>();
for (int i = 0; i < listBookHtml.size(); i++) {
String htmlString = listBookHtml.get(i).getHtmlContent(); //书页html内容
// 将图片oss地址转换为内网地址
htmlString = htmlString.replace("oss-cn-hangzhou.aliyuncs.com", "oss-cn-hangzhou-internal.aliyuncs.com");
String outputName = book.getBookName()+"_"+(i+1);
// 组装待合并的PDF路径
filesInFolder.add(outputName+".pdf");
// 有水印版本
if(!PDFUtil.createPDF(ftlPath, ftlName, watermarkYesPath, outputName, htmlString, true)) {
System.out.println("====>出现异常,导致错误的原因可能是html源码格式错误或模板错误,请联系管理员。");
}
// 无水印版本
if(!PDFUtil.createPDF(ftlPath, ftlName, watermarkNoPath, outputName, htmlString, false)) {
System.out.println("====>出现异常,导致错误的原因可能是html源码格式错误或模板错误,请联系管理员。");
}
System.out.println("====>第 【"+(i+1)+"】 张生成完成");
// 封面页PDF生成图片base64
if(i==0) {
String base64 = PDFUtil.transPDFBase64(watermarkNoPath,outputName, 20);
book.setBookLogoImg(base64);
bookDao.update(book);
}
}
System.out.println("====>开始合并");
PDFUtil.mergePDF(watermarkYesPath, filesInFolder, book.getBookName()+"_清样.pdf");
PDFUtil.mergePDF(watermarkNoPath, filesInFolder, book.getBookName()+"_原稿.pdf");
System.out.println("========================= PDF结束生成 【"+book.getBookName()+"】 =========================");
// 得到本地合并后的pdf路径
String editionDownloadUrlYes = watermarkYesPath+"/"+book.getBookName()+"_清样.pdf";
String editionDownloadUrlNo = watermarkNoPath+"/"+book.getBookName()+"_原稿.pdf";
// 清样上传到阿里云oss
System.out.println("====>清样上传到阿里云oss");
String keyYes = "generatePDF/"+providerId+"_"+providerName+"/"+ftlName+"/Watermarks/"+book.getBookName()+"_清样.pdf";
File fileYes = new File(editionDownloadUrlYes);
Map<String,Object> mapYes = OssUtil.uploadFileOss("jointat-generate", keyYes, fileYes);
// 原稿上传到阿里云oss
System.out.println("====>原稿上传到阿里云oss");
String keyNo = "generatePDF/"+providerId+"_"+providerName+"/"+ftlName+"/Manuscript/"+book.getBookName()+"_原稿.pdf";
File fileNo = new File(editionDownloadUrlNo);
Map<String,Object> mapNo = OssUtil.uploadFileOss("jointat-generate", keyNo, fileNo);
// 更新PDF为可下载状态、下载地址
String linkYes = mapYes.get("link").toString();
String linkNo = mapNo.get("link").toString();
if(!linkYes.equals("false") && !linkNo.equals("false")) {
System.out.println("====>更新谱书为可下载状态");
bookEditionDao.updateEditionYes(bookId, "1", linkYes, linkNo);
}
// 删除文件夹
PDFUtil.delAllFile(watermarkYesPath);
PDFUtil.delAllFile(watermarkNoPath);
}
* html生成pdf
* @param ftlPath ftl模板目录路径
* @param ftlName ftl模板名称
* @param outputPath pdf输出路径
* @param outputName pdf输出名称
* @param htmlString html源码
* @param watermark 是否添加水印【true-添加、false=不添加】
* @return
public static boolean createPDF(String ftlPath,String ftlName,String outputPath,String outputName,String htmlString,boolean watermark) {
FileOutputStream os = null;
try {
// 创建一个freemarker.template.Configuration实例,它是存储 FreeMarker
Configuration cf = new Configuration(Configuration.VERSION_2_3_22); // 指定版本号
cf.setDirectoryForTemplateLoading(new File(ftlPath)); // 设置模板目录
cf.setDefaultEncoding("UTF-8"); // 设置默认编码格式
Template temp = cf.getTemplate(ftlName+".ftl"); // 从设置的目录中获得模板
// html转换为xhtml
ByteArrayInputStream inputStream = new ByteArrayInputStream(htmlString.getBytes("UTF-8"));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 使用jtidy解析
Tidy tidy = new Tidy(); // 实例化Tidy对象
tidy.setInputEncoding("UTF-8"); // 设置输入
tidy.setQuiet(false); // 如果是true 不输出注释,警告和错误信息
tidy.setShowWarnings(false); // 不显示警告信息
tidy.setIndentContent(false); // 缩进适当的标签内容。
tidy.setSmartIndent(false); // 内容缩进
tidy.setIndentAttributes(false);
tidy.setPrintBodyOnly(true); // 只输出body内部的内容
tidy.setWraplen(1024); // 多长换行
tidy.setXHTML(true); // 输出为xhtml
tidy.setTrimEmptyElements(false); // 不输出空元素
tidy.setMakeClean(false); // 去掉没用的标签
tidy.setWord2000(false); // 清洗word2000的内容
tidy.setErrout(new PrintWriter(System.out)); // 设置错误输出信息
tidy.parse(inputStream, outputStream);
// 从模板生成html文件
String fileHtml = "hzSur.html";
File file = new File(fileHtml);
if (!file.exists()) {
file.createNewFile();
}
// 是否构造水印模板
String htmlWatermark = "";
if(watermark) {
htmlWatermark = "<div class='outFlows'>";
for (int i = 0; i < 10; i++) {
htmlWatermark += "<div></div>";
}
htmlWatermark +="</div>";
}
// 将数据写入模板
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file),"UTF-8"));
Map<String, Object> product = new HashMap<String, Object>();
product.put("htmlWatermark", htmlWatermark);
product.put("htmlString", outputStream.toString());
temp.process(product, out);
out.close();
// 定义输出目录路径及文件名称
File outDir =new File(outputPath);
outDir.mkdirs();
ITextRenderer renderer = new ITextRenderer();
os = new FileOutputStream(outputPath+"/"+outputName+".pdf");
renderer.setDocument(new File(fileHtml).toURI().toURL().toString());
// 获取中文字体
ITextFontResolver fontResolver = renderer.getFontResolver();
fontResolver.addFont(ftlPath+"/MSYH.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont(ftlPath+"/SIMSUN.TTC", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont(ftlPath+"/SIMSUN.TTC", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont(ftlPath+"/STKAITI.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont(ftlPath+"/SIMLI.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont(ftlPath+"/simfang.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont(ftlPath+"/FZSTK.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont(ftlPath+"/FZYTK.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont(ftlPath+"/simhei.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont(ftlPath+"/SIMYOU.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
// 输出
renderer.layout();
renderer.createPDF(os);
renderer.finishPDF();
renderer = null;
os.close();
return true;
} catch (Exception e) {
System.out.println(e);
e.printStackTrace();
return false;
}
}