目錄
- 前言
- 思路一:直接匯出pdf
- 使用itext模板匯出pdf
- 思路二:先匯出word再轉成pdf
- 1)匯出word
- 2)word轉pdf
- 最終方案
docx4jspire.doc.free + freemarker
前言
本文為我搜集的根據模板匯出PDF的方法整理而來,所以貼了好多帖子的鏈接,有的方法僅適合特殊的業務場景,可以根據業務需求選擇合適的方法,寫的不好請輕噴,
思路一:直接匯出pdf
使用itext模板匯出pdf
-
適用范圍
業務生成的 pdf 是具有固定格式或者模板的文字及其圖片等內容,使用模板,只需要將不一致的地方改成文本域,然后進行文字填充就可以了;如果涉及的業務不能有模塊化可以提取出來東西,從開頭一步一步去繪畫,
-
參考鏈接
JAVA 使用Itext模板生成pdf,解決圖片插入,文本域超出字體縮放,半自動換行
java根據模板生成pdf檔案并匯出 -
缺點
超出文本域的部分的文字(若不設定自動調整文字大小)則會不顯示,無法自動分頁,(暫未找到解決方案)
思路二:先匯出word再轉成pdf
1)匯出word
-
Freemarker
官方參考手冊:http://freemarker.foofun.cn/toc.html
-
Freemarker 將資料填入 .ftl 模板匯出 word(.doc/.docx)
-
參考鏈接:
SpringBoot整合Freemarker匯出word檔案表格
freemarker匯出Word,文本,可回圈表格,合并單元格,可回圈圖片,目錄更新(一)
-
缺點:
匯出的 .doc / .docx 實際上是 xml 檔案,用辦公軟體能正常打開使用,但是轉 PDF 的時候發現轉不成功,轉過之后的 PDF 顯示的不是 word 的格式字符,而是像 xml 檔案的標簽及字符,
-
-
Freemarker 結合 .docx 格式的本質將資料填入 .docx 里面的 document.xml 檔案匯出 .docx
-
參考鏈接:
freemarker動態生成word并將生成的word轉為PDF
-
優點:
可轉換為 pdf
-
相關錯誤:
A. Date 格式的資料傳輸報錯!
解決方案:${(initialTime?string("yyyy-MM-dd HH:mm:ss"))!} -
附:
a. 回圈行及表單行是否顯示功能參考鏈接:
SpringBoot整合Freemarker匯出word檔案表格
freemarker合并單元格,if、else標簽的使用,null、空字串處理
如:

匯出結果如下:

也可向參考鏈接2一樣,在 xml 上定義插入對應的合并代碼(如下圖),

-
-
-
docx4j
結合 .docx 格式的本質將資料填入 .docx 里面的 document.xml 檔案匯出 .docx
docx4j 中模板的使用
docx4j 實作動態表格(模板式)
docx4j 中圖片的使用(模板式)
docx4j 實作動態表格(模板式)單元格合并(含多列并列合并)
-
POI
Poi的匯出word和轉pdf --已試過,可行
Java檔案操作之word轉pdf并匯出(liunx和windows)
-
Aspose.word(需要license)(未嘗試)
使用Aspose.word (Java) 填充word檔案資料(包含圖片填充)
2)word轉pdf
-
docx4j 將 .docx 轉 pdf
官方下載地址
-
方法一:使用 docx4j2.8.1在 docx 模板填入資料并且轉 pdf
參考鏈接:docx4j Word檔案轉換pdf- 解決中文問題和變數替換
-
方法二:將 docx 轉 pdf
參考鏈接:freemarker動態生成word并將生成的word轉為PDF
-
相關錯誤:
A.匯出的 PDF 亂碼(檢查 word 檔案中的字體是否在字體庫中)
使用docx4j實作docx轉pdf(解決linux環境下中文亂碼問題)

B.注意:存在樣式 bug!!!
暫未找到解決方案,
-
-
Spire.Doc 實作 word (.doc / .docx)轉 pdf(嘗試了免費版,可行)
有付費版和免費版,免費版僅支持三頁內的 word 轉 pdf
-
aspose.word 將 word 轉 pdf (未嘗試)
需要license,破解方法1,破解方法2
最終方案
docx4j spire.doc.free + freemarker
-
模板準備
將占位變數名寫在模板 testTemplate.docx的對應位置上,用
${}包起來,
把復制一份副本,將副本 .docx 的后綴名重命名為 .zip,解壓后找到 /word/document.xml,編譯器打開檔案,代碼格式化后,對比例如 ${repliedUserId} 的占位引數是否被拆開(如果拆開需手動修改),修改名字為 testDocument.xml,
將源模板 testTemplate.docx 和 testDocument.xml 放到相應位置,
-
maven依賴
<dependencies> <!-- 動態生成word--> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.22</version> </dependency> <!-- docx轉pdf--> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j-JAXB-Internal</artifactId> <version>8.2.4</version> </dependency> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j-export-fo</artifactId> <version>8.2.4</version> </dependency> <!-- https://www.e-iceblue.cn/spiredocforjava/spire-doc-for-java-program-guide-content.html--> <!-- https://mvnrepository.com/artifact/e-iceblue/spire.doc.free --> <dependency> <groupId>e-iceblue</groupId> <artifactId>spire.doc.free</artifactId> <version>5.2.0</version> </dependency> </dependencies> <repositories> <repository> <id>com.e-iceblue</id> <name>e-iceblue</name> <url>https://repo.e-iceblue.cn/repository/maven-public/</url> </repository> </repositories> -
Controller
@PostMapping("/pdfExport") public ResponseEntity exportPdf(@RequestParam Map<String, Object> params) { try { // 查找業務資料 TestEntity testEntity = testService.querySheet(params); // 格式轉換時的暫存檔案名 String fileUuid = UUID.randomUUID().toString().replaceAll("-", ""); String toDocxPath = "E://project//test//ToPDF//word//" + fileUuid + ".docx"; String toPdfPath = "E://project//test//ToPDF//pdf//" + fileUuid + ".pdf"; String toXmlPath = "E://project//test//ToPDF//xml//" + fileUuid + ".xml"; String docxTemplate = "E://project//test//ToPDF//template//testTemplate.docx"; // .xml轉.docx(testDocument.xml表示在專案的相對路徑下) XmlToDocx.toDocx("testDocument.xml",docxTemplate, toXmlPath, toDocxPath, testEntity); // .docx轉.pdf WordToPdf.docxToPdf(toDocxPath, toPdfPath); // 下載pdf并洗掉本地pdf ResponseEntity response = WordToPdf.downloadPdf("這是PDF的名字啊", toPdfPath); return response; } catch (Exception e) { throw new BusinessException("下載PDF失敗!" + e.getMessage()); } } -
XmlToDocx類
import java.io.*; import java.util.Enumeration; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; /** * 其實docx屬于zip的一種,這里只需要操作word/document.xml中的資料,其他的資料不用動 * * @author * */ public class XmlToDocx { /** * * @param xmlTemplate xml的檔案名 * @param docxTemplate docx的路徑和檔案名(.docx模板) * @param xmlTemp 填充完資料的臨時xml * @param toFilePath 目標檔案名 * @param object 需要動態傳入的資料 */ public static void toDocx(String xmlTemplate, String docxTemplate, String xmlTemp, String toFilePath, Object object) { try { // 1.object是動態傳入的資料 // 這個地方不能使用FileWriter因為需要指定編碼型別否則生成的Word檔案會因為有無法識別的編碼而無法打開 // Writer w1 = new OutputStreamWriter(new FileOutputStream(xmlTemp), "gb2312"); Writer w1 = new OutputStreamWriter(new FileOutputStream(xmlTemp), "utf-8"); // 2.把object中的資料動態由freemarker傳給xml XmlTplUtil.process(xmlTemplate, object, w1); // 3.把填充完成的xml寫入到docx中 XmlToDocx xtd = new XmlToDocx(); File xmlTempFile = new File(xmlTemp); xtd.outDocx(xmlTempFile, docxTemplate, toFilePath); // 洗掉臨時xml檔案 xmlTempFile.delete(); }catch (Exception e) { e.printStackTrace(); } } /** * * @param documentFile 動態生成資料的docunment.xml檔案 * @param docxTemplate docx的模板 * @param toFilePath 需要匯出的檔案路徑 * @throws ZipException * @throws IOException */ public void outDocx(File documentFile, String docxTemplate, String toFilePath) throws ZipException, IOException { try { File docxFile = new File(docxTemplate); ZipFile zipFile = new ZipFile(docxFile); Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries(); ZipOutputStream zipout = new ZipOutputStream(new FileOutputStream(toFilePath)); int len = -1; byte[] buffer = new byte[1024]; while (zipEntrys.hasMoreElements()) { ZipEntry next = zipEntrys.nextElement(); InputStream is = zipFile.getInputStream(next); // 把輸入流的檔案傳到輸出流中 如果是word/document.xml由我們輸入 zipout.putNextEntry(new ZipEntry(next.toString())); if ("word/document.xml".equals(next.toString())) { InputStream in = new FileInputStream(documentFile); while ((len = in.read(buffer)) != -1) { zipout.write(buffer, 0, len); } in.close(); } else { while ((len = is.read(buffer)) != -1) { zipout.write(buffer, 0, len); } is.close(); } } zipout.close(); } catch (Exception e) { e.printStackTrace(); } } } -
WordToPdf類
import org.apache.commons.io.IOUtils; import org.docx4j.Docx4J; import org.docx4j.fonts.IdentityPlusMapper; import org.docx4j.fonts.Mapper; import org.docx4j.fonts.PhysicalFonts; import org.docx4j.openpackaging.exceptions.Docx4JException; import org.docx4j.openpackaging.packages.WordprocessingMLPackage; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import java.io.*; import java.net.URLEncoder; public class WordToPdf { /** * (Docx4J).docx轉.pdf(當docx的一行全是英文以及標點符號時轉換的Pdf那一行會超出范圍 https://segmentfault.com/q/1010000043372748) * @param docxPath docx檔案路徑 * @param pdfPath 輸出的pdf檔案路徑 * @throws Exception */ @Deprecated public static boolean docxToPdf(String docxPath, String pdfPath) throws Exception { FileOutputStream out = null; try { File docxfile = new File(docxPath); WordprocessingMLPackage pkg = Docx4J.load(docxfile); Mapper fontMapper = new IdentityPlusMapper(); fontMapper.put("隸書", PhysicalFonts.get("LiSu")); fontMapper.put("宋體", PhysicalFonts.get("SimSun")); fontMapper.put("微軟雅黑", PhysicalFonts.get("Microsoft Yahei")); fontMapper.put("黑體", PhysicalFonts.get("SimHei")); fontMapper.put("楷體", PhysicalFonts.get("KaiTi")); fontMapper.put("新宋體", PhysicalFonts.get("NSimSun")); fontMapper.put("華文行楷", PhysicalFonts.get("STXingkai")); fontMapper.put("華文仿宋", PhysicalFonts.get("STFangsong")); fontMapper.put("仿宋", PhysicalFonts.get("FangSong")); fontMapper.put("幼圓", PhysicalFonts.get("YouYuan")); fontMapper.put("華文宋體", PhysicalFonts.get("STSong")); fontMapper.put("華文中宋", PhysicalFonts.get("STZhongsong")); fontMapper.put("等線", PhysicalFonts.get("SimSun")); fontMapper.put("等線 Light", PhysicalFonts.get("SimSun")); fontMapper.put("華文琥珀", PhysicalFonts.get("STHupo")); fontMapper.put("華文隸書", PhysicalFonts.get("STLiti")); fontMapper.put("華文新魏", PhysicalFonts.get("STXinwei")); fontMapper.put("華文彩云", PhysicalFonts.get("STCaiyun")); fontMapper.put("方正姚體", PhysicalFonts.get("FZYaoti")); fontMapper.put("方正舒體", PhysicalFonts.get("FZShuTi")); fontMapper.put("華文細黑", PhysicalFonts.get("STXihei")); fontMapper.put("宋體擴展", PhysicalFonts.get("simsun-extB")); fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312")); fontMapper.put("新細明體", PhysicalFonts.get("SimSun")); pkg.setFontMapper(fontMapper); out = new FileOutputStream(pdfPath); //docx4j docx轉pdf FOSettings foSettings = Docx4J.createFOSettings(); // foSettings.setWmlPackage(pkg); foSettings.setOpcPackage(pkg); Docx4J.toFO(foSettings, out, Docx4J.FLAG_EXPORT_PREFER_XSL); // Docx4J.toPDF(pkg, out); // 洗掉源.docx檔案 docxfile.delete(); return true; // } catch (FileNotFoundException e) { // e.printStackTrace(); // } catch (Docx4JException e) { // e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); return false; } finally { if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * https://www.e-iceblue.cn/spiredocforjavaconversion/java-convert-word-to-pdf.html * (spire.doc.free: * 免費版有篇幅限制,在加載或保存 Word 檔案時,要求 Word 檔案不超過 500 個段落,25 個表格, * 同時將 Word 檔案轉換為 PDF 和 XPS 等格式時,僅支持轉換前三頁,) * (spire.doc.free)word轉pdf * @param wordInPath word輸入路徑 * @param pdfOutPath Pdf輸出路徑 * @return */ public static boolean convertWordToPdf(String wordInPath, String pdfOutPath) { try { //實體化Document類的物件 Document doc = new Document(); //加載Word doc.loadFromFile(wordInPath); //保存為PDF格式 doc.saveToFile(pdfOutPath, FileFormat.PDF); return true; } catch (Exception e) { // e.printStackTrace(); return false; } finally { // 洗掉源word檔案 File docxfile = new File(wordInPath); if (docxfile.exists()) { docxfile.delete(); } } } }
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/545521.html
標籤:Java
