文章目錄
- pdf生成方案
- dot轉pdf
- html轉pdf
- openhtmltopdf
- aspose-pdf
- 實踐
- playwright
- 實踐
- 圖片轉pdf
- Apache PDFBox實踐
- 框架場景匹配
- 后記
????前言:隨著客戶對報告審美的提升,需求也越來越五彩斑斕~ 原有的dot模板已經滿足不了他們了!這篇文章主打列出各種方案及適用場景,帶部分demo。
pdf生成方案
dot轉pdf
自定義.dot
文檔,模板中插入書簽占位,使用aspose-words
轉換為pdf
ps:這個收費=_=|| 公司之前有項目用了,所以沒探索其他實現方案
html轉pdf
探索了三個框架,openhtmltopdf
、aspose-pdf
、flying-saucer-pdf-openpdf
,下面分別對這些方案進行描述。
openhtmltopdf
源碼地址
明晃晃的優點:
- 開源&&免費
但有兩個不得不忽視的缺點:
- 中文亂碼,官網issue中有人提單了含有中文字符的html輸出pdf有亂碼 #129
,按照解決方案并不能修復,所以block了 - 僅僅支持簡單的CSS
aspose-pdf
官網:Creating a complex PDF,雖然和aspose-words
都是aspose家的,但他們分開收費!!!
優點:
- 內置14種字體 - 中文支持度非常高
- 支持加密/解密、數字簽名、權限控制
- 文檔完善、社區活躍度高
缺點:
- 貴!!!經費不足不考慮
- 對CSS3中部分樣式不支持,例如
aspect-ratio
,需要后端一點點排查再讓前端調整。。。 (太難了)
實踐
注意: 這個庫不是在maven中央倉庫中管理,需要加一個倉庫配置https://releases.aspose.com/java/repo/
<dependency><groupId>com.aspose</groupId><artifactId>aspose-pdf</artifactId><version>23.6</version>
</dependency>
import com.aspose.pdf.Document;
import com.aspose.pdf.HtmlLoadOptions;
import com.aspose.pdf.SaveFormat;public void generatePdf(String name) { // 1. 準備數據模型Map<String, Object> data = new HashMap<>();data.put("name", name);data.put("date", LocalDate.now().toString());String html = freeMarkerService.getTemplate2String("report.ftl", data);try (FileOutputStream fileOutputStream = new FileOutputStream("E:\\test\\report.pdf");InputStream stream = new ByteArrayInputStream(html.getBytes("UTF-8"));) {// 加載靜態資源HtmlLoadOptions loadOptions = new HtmlLoadOptions("src/main/resources/static/report");loadOptions.setEmbedFonts(true);Document document = new Document(stream, loadOptions);document.save(fileOutputStream, SaveFormat.Pdf);} catch (Exception e) {log.error("[generatePdf] html轉pdf失敗", e);}
}
playwright
由前端提供的html文件,里面包含的CSS樣式太復雜了,沒辦法只能用webkit這種方式渲染樣式才不會有大的偏差~
優點:
- 渲染質量ok,基本上和html展示一致
- 開源免費
- 跨平臺支持,docker中也可運行
缺點:
- 初次運行要下載瀏覽器
- 資源消耗大,每個轉換需要100-300M內存
- Java版是對Node.js版的封裝
實踐
maven
<dependency><groupId>com.microsoft.playwright</groupId><artifactId>playwright</artifactId><version>1.52.0</version>
</dependency>
業務代碼(強制將輸出A4紙張大小):
package com.lizzy.mp.service;import java.io.FileOutputStream;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;import org.springframework.stereotype.Service;import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.options.Margin;import lombok.extern.slf4j.Slf4j;@Service
@Slf4j
public class PlaywrightPdfService {@Resourceprivate FreeMarkerService freeMarkerService;private Playwright playwright;private Browser browser;@PostConstructpublic void init() {playwright = Playwright.create();browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(true).setArgs(Stream.of("--disable-dev-shm-usage").collect(Collectors.toList())));}public void generatePdf(String name) {// 1. 準備數據模型Map<String, Object> data = new HashMap<>();data.put("name", name);data.put("date", LocalDate.now().toString());String htmlContent = freeMarkerService.getTemplate2String("report2.ftl", data);try (Page page = browser.newPage();FileOutputStream fileOutputStream = new FileOutputStream("E:\\test\\report.pdf");) {page.setContent(htmlContent);byte[] bytes = page.pdf(new Page.PdfOptions().setMargin(new Margin().setTop("0cm").setBottom("0cm").setLeft("0cm").setRight("0cm")).setPrintBackground(true).setFormat("A4"));fileOutputStream.write(bytes);} catch (Exception e) {log.error("[generatePdf] HTML轉PDF失敗", e);}log.info("[generatePdf] HTML轉PDF成功");}@PreDestroypublic void cleanup() {if (browser != null) {browser.close();}if (playwright != null) {playwright.close();}}
}
圖片轉pdf
??????項目中前后端共用一個html模板,前端會有預覽功能,于是乎討論出一個方案:前端直接將html生成圖片,后端將圖片轉成pdf,這樣后端就不用care樣式問題了!
??????網上解決方案很多,作者只調研了Apache PDFBox。
Apache PDFBox實踐
maven:
<dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>3.0.3</version>
</dependency>
業務代碼,說明:
- 方法
convert
中生成的pdf打開后50%展示都很大 - 方法
convertForA4
中進行了限制,打開后100%還原
ps:最根本的解決方法還是控制css樣式為A4
package com.lizzy.mp.service;import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;import javax.imageio.ImageIO;import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.springframework.stereotype.Service;import lombok.extern.slf4j.Slf4j;@Service
@Slf4j
public class Image2PdfService {public void convert() {// 創建PDF文檔try (PDDocument document = new PDDocument()) {// 加載圖片PDImageXObject pdImage = PDImageXObject.createFromFile("E:\\report_page-0001.jpg", document);// 創建頁面,大小與圖片相同PDPage page = new PDPage(new PDRectangle(pdImage.getWidth(), pdImage.getHeight()));document.addPage(page);// 將圖片寫入PDFtry (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {contentStream.drawImage(pdImage, 0, 0);}// 保存PDFdocument.save("E:\\test\\report0.pdf");} catch (IOException e) {log.error("[convert] 圖片轉pdf失敗,錯誤原因:{}", e.getMessage(), e);}}public void convertForA4() {String imagePath = "E:\\report_page-0001.jpg";String outputPdfPath = "E:\\test\\report0.pdf";try (PDDocument document = new PDDocument()) {// 讀取圖片BufferedImage image = ImageIO.read(new File(imagePath));if (image == null) throw new IOException("無法讀取圖片");PDImageXObject pdImage = PDImageXObject.createFromFile(imagePath, document);// 創建A4頁面PDRectangle a4 = PDRectangle.A4;PDPage page = new PDPage(a4);document.addPage(page);// 原始圖片尺寸float imageWidth = image.getWidth();float imageHeight = image.getHeight();// A4尺寸float pageWidth = a4.getWidth();float pageHeight = a4.getHeight();// 縮放比例(等比縮放)float scale = Math.min(pageWidth / imageWidth, pageHeight / imageHeight);float drawWidth = imageWidth * scale;float drawHeight = imageHeight * scale;// 居中坐標float x = (pageWidth - drawWidth) / 2;float y = (pageHeight - drawHeight) / 2;// 寫入圖像try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {contentStream.drawImage(pdImage, x, y, drawWidth, drawHeight);}// 保存PDFdocument.save(outputPdfPath);} catch (IOException e) {System.err.println("圖片轉PDF失敗: " + e.getMessage());e.printStackTrace();}}
}
框架場景匹配
框架名稱 | CSS樣式支持度 | 是否開源 | 中文支持 | 使用難易 | 體積大小 | 說明 |
---|---|---|---|---|---|---|
Aspose.word | 中等 | ?,收費高 試用版有水印 | 高 | 簡單 | 50+MB | |
Aspose.pdf | CSS3(動畫等不支持) | ?,收費高 試用版有水印 | 高 | 中等 | 大 | |
openhtmltopdf | CSS2.1(基本支持) | ? | 需顯示引入字體 | 簡單 | 小,3~5MB | |
playwright | 非常高 | ? | 瀏覽器原生支持 | 需運行瀏覽器依賴 | 大,依賴Chromium | |
apache pdfbox | - | ? | - | - | - | 只使用圖像轉pdf,無需控制樣式 |
后記
因項目背景,對報告pdf的生成要求蠻高,所以得不停嘗試各種解決方案,推薦獲取思路的網址~
- 想找個生成PDF的庫或者解決方案,不要Aspose的(from www.reddit.com),雖是C#解決方案,但道理相通
- Pdf generation(from www.reddit.com)
ps:思考要記錄,不然會忘記~