springboot生成pdf方案之dot/html/圖片轉pdf三種方式

文章目錄

  • pdf生成方案
    • dot轉pdf
    • html轉pdf
      • openhtmltopdf
      • aspose-pdf
        • 實踐
      • playwright
        • 實踐
    • 圖片轉pdf
      • Apache PDFBox實踐
  • 框架場景匹配
  • 后記

????前言:隨著客戶對報告審美的提升,需求也越來越五彩斑斕~ 原有的dot模板已經滿足不了他們了!這篇文章主打列出各種方案及適用場景,帶部分demo。

pdf生成方案

dot轉pdf

自定義.dot文檔,模板中插入書簽占位,使用aspose-words轉換為pdf
ps:這個收費=_=|| 公司之前有項目用了,所以沒探索其他實現方案

html轉pdf

探索了三個框架,openhtmltopdfaspose-pdfflying-saucer-pdf-openpdf,下面分別對這些方案進行描述。

openhtmltopdf

源碼地址

明晃晃的優點:

  1. 開源&&免費

但有兩個不得不忽視的缺點:

  1. 中文亂碼,官網issue中有人提單了含有中文字符的html輸出pdf有亂碼 #129
    ,按照解決方案并不能修復,所以block了
  2. 僅僅支持簡單的CSS

aspose-pdf

官網:Creating a complex PDF,雖然和aspose-words都是aspose家的,但他們分開收費!!!
優點:

  1. 內置14種字體 - 中文支持度非常高
  2. 支持加密/解密、數字簽名、權限控制
  3. 文檔完善、社區活躍度高

缺點:

  1. 貴!!!經費不足不考慮
  2. 對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這種方式渲染樣式才不會有大的偏差~
優點:

  1. 渲染質量ok,基本上和html展示一致
  2. 開源免費
  3. 跨平臺支持,docker中也可運行

缺點:

  1. 初次運行要下載瀏覽器
  2. 資源消耗大,每個轉換需要100-300M內存
  3. 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.pdfCSS3(動畫等不支持)?,收費高
試用版有水印
中等
openhtmltopdfCSS2.1(基本支持)?需顯示引入字體簡單小,3~5MB
playwright非常高?瀏覽器原生支持需運行瀏覽器依賴大,依賴Chromium
apache pdfbox-?---只使用圖像轉pdf,無需控制樣式

后記

因項目背景,對報告pdf的生成要求蠻高,所以得不停嘗試各種解決方案,推薦獲取思路的網址~

  • 想找個生成PDF的庫或者解決方案,不要Aspose的(from www.reddit.com),雖是C#解決方案,但道理相通
  • Pdf generation(from www.reddit.com)

ps:思考要記錄,不然會忘記~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/88464.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/88464.shtml
英文地址,請注明出處:http://en.pswp.cn/web/88464.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

前端開發—全棧開發

全棧開發者在面試前端或全棧崗位時&#xff0c;自我介紹需要巧妙融合“技術廣度”與“崗位針對性”&#xff0c;避免成為泛泛而談的“樣樣通樣樣松”。以下是結合面試官關注點和全棧特性的專業介紹策略&#xff1a;&#x1f9e0; 一、自我介紹的核心理念 突出全棧優勢&#xff…

Redis生產環境過期策略配置指南:務實落地,避免踩坑

在生產環境中合理配置Redis過期策略是保障系統穩定性和內存效率的關鍵。以下配置建議基于實戰經驗&#xff0c;避免理論堆砌&#xff0c;直擊核心要點&#xff1a;一、核心策略配置&#xff1a;惰性刪除 定期刪除&#xff08;默認已啟用&#xff09;無需額外配置&#xff1a;R…

Ubuntu 20.04 安裝 Node.js 20.x、npm、cnpm 和 pnpm 完整指南

&#x1f310; Ubuntu 20.04 安裝 Node.js 20.x、npm、cnpm 和 pnpm 完整指南 &#x1f680; 在本文中&#xff0c;我們將介紹如何在 Ubuntu 20.04 上安裝 Node.js 20.x&#xff0c;以及如何安裝 npm、cnpm 和 pnpm 來提高開發效率 ?。1?? 安裝 Node.js 20.x 為了確保使用最…

【時時三省】(C語言基礎)通過指針引用數組元素

山不在高&#xff0c;有仙則名。水不在深&#xff0c;有龍則靈。 ----CSDN 時時三省引用一個數組元素&#xff0c;可以用下面兩種方法&#xff1a;( 1 )下標法&#xff0c;如a[i]形式&#xff1b;( 2 )指針法&#xff0c;如* ( a i )或* ( p i )。其中a是數組名&#xff0c;p…

Guava LoadingCache

LoadingCache 是 Google Guava 庫提供的一個高級緩存實現&#xff0c;它通過自動加載機制簡化了緩存使用模式。核心特性自動加載機制當緩存未命中時&#xff0c;自動調用指定的 CacheLoader 加載數據線程安全&#xff1a;并發請求下&#xff0c;相同key只會加載一次靈活的過期策…

基于LSTM-GRU模型的黃金價格動態監測:關稅政策與美指的量化關聯研究

摘要&#xff1a;本文通過BERT-Large模型對關稅政策進行語義解析&#xff0c;結合LSTM-GRU混合模型、DCC-GARCH動態相關性模型及蒙特卡洛情景分析&#xff0c;量化解析7月11日黃金價格異動背后的三大驅動因子——政策沖擊、美元指數壓制與美聯儲政策不確定性&#xff0c;提供AI…

V少JS基礎班之第七彈

文章目錄一、 前言二、本節涉及知識點三、重點內容1、prototype2、constructor3、中場回顧&總結4、__ proto__5、第二次中場回顧&總結6、原型鏈6、第三次中場回顧&總結7、原型鏈中的奇點一、 前言 第七彈內容是原型鏈。網絡上原型鏈的資料很多。但是我看了很多篇&…

Nuxt3自動打包及自動修改端口號腳本

Nuxt3自動打包及自動修改端口號腳本技術文章大綱 背景與需求 Nuxt3作為現代Vue框架&#xff0c;開發中常需處理打包部署和端口配置問題。自動化腳本可提升效率&#xff0c;減少手動操作錯誤。 實現自動打包 利用Nuxt3內置命令結合Node.js腳本實現自動化構建。通過npm run build…

紅海云國資案例之多層級工貿集團的一體化HR平臺建設實戰

在中國經濟邁向高質量發展的進程中&#xff0c;國有企業作為重要的經濟支柱和行業引領者&#xff0c;正面臨著數字化轉型的深刻變革。F集團作為G市首家實現工貿一體化運營的大型企業&#xff0c;位列中國輕工業百強&#xff0c;其在人力資源數字化轉型中的探索和實踐&#xff0…

TCP詳解——流量控制、滑動窗口

目錄 流量控制 滑動窗口 丟包重傳 情況一&#xff1a;數據到達&#xff0c;應答丟失 情況二&#xff1a;數據包丟失 流量控制 TCP協議會根據接收端的緩沖區大小來調整發送速度&#xff0c;剩余空間多則發送速度快&#xff0c;否則降低發送速度 接收端將??可以接收的緩…

C#高級特性面試問題的詳細分析,涵蓋核心概念、應用場景和最佳實踐

序列化與反序列化 1. 什么是序列化和反序列化&#xff1f;用途是什么&#xff1f; // 序列化示例 Person person new Person { Name "Alice", Age 30 }; string json JsonSerializer.Serialize(person); // 序列化為JSON// 反序列化示例 Person deserialized Js…

【電腦】內存的基礎知識

內存&#xff08;Memory&#xff09;是計算機中用于臨時存儲數據和程序的地方&#xff0c;它直接影響到系統的運行速度和性能。以下是關于內存的詳細知識&#xff1a;1. 內存類型常見的內存類型包括以下幾個主要種類&#xff1a;SDRAM (Synchronous Dynamic Random Access Memo…

Java---IDEA

IDEA概述 IDEA&#xff1a;全稱Intellij IDEA&#xff0c;是用于Java語言開發的集成開發環境 集成環境&#xff1a;把代碼編寫&#xff0c;編譯&#xff0c;運行&#xff0c;調試等多種功能綜合到一起的開發工具 下載與安裝 下載&#xff1a;IntelliJ IDEA – the IDE for …

【每日刷題】x 的平方根

69. x 的平方根 - 力扣&#xff08;LeetCode&#xff09; 方法一&#xff1a;暴力 從0開始遍歷&#xff0c;直到 ans*ans > x 為止&#xff0c;這時ans-1就是答案。需要注意可能會爆int&#xff0c;所以ans要開為long&#xff0c;最后再轉換為int。 class Solution {publ…

C#元組:從基礎到實戰的全方位解析

C#元組&#xff1a;從基礎到實戰的全方位解析 在 C# 編程中&#xff0c;元組&#xff08;Tuple&#xff09;是一種輕量級的數據結構&#xff0c;用于臨時存儲多個不同類型的元素。無論是方法返回多個值、LINQ 查詢中的臨時投影&#xff0c;還是簡化數據傳遞&#xff0c;元組都以…

Django母嬰商城項目實踐(二)

2、母嬰商城項目環境配置 環境配置: Python3.12 解釋器Pycharm Professional 2025.1 編輯器Django 4.2(或 Django 5.x)MySQL 8.0.28 數據庫 1、Django框架 介紹 Django是一個高級的Python Web應用框架,可以快速開發安全和可維護的網站。由經驗豐富的開發者構建,Django負責…

Go語言的Channel通道的含義。區分緩沖通道和非緩沖通道,并討論通道的發送、接收、關閉以及如何安全地從已關閉的通道讀取數據。

非緩沖通道&#xff1a;非緩沖通道在確定時沒有聲明容量大小&#xff0c;發送和接收操作會同步阻塞&#xff0c;直到另一端準備好。發送方和接收方必須同時就緒才能完成數據交換&#xff0c;否則會阻塞。常用于goroutine之間的同步通信。緩沖通道&#xff1a;緩沖通道在確定時就…

tensor

&#x1f609;如果您想用jupyter notebook跑我的筆記&#xff0c;可以在下面獲取ipynb版本 &#x1f60a;麻煩給個免費的star&#x1f618; ??主包也更建議這種形式&#xff0c;上面的筆記也更加全面&#xff0c;每一步都有直觀的輸出 文章目錄&#x1f4da; PyTorch張量操作…

STM32-DAC數模轉換

DAC數模轉換&#xff1a;將數字信號轉換成模擬信號特性&#xff1a;2個DAC轉換器每個都擁有一個轉換通道8位或12位單調輸出&#xff08;8位右對齊&#xff1b;12位左對齊右對齊&#xff09;雙ADC通道同時或者分別轉換外部觸發中斷電壓源控制部分&#xff08;外部觸發3個APB1&am…

前后端集合如何傳遞

前端vue后端rest風格&#xff1a;1.路徑傳參&#xff08;參數必傳&#xff09;&#xff0c;通過pathvarible注解后端&#xff1a;DeleteMapping("/{YYIDs}")public R<Void> remove(NotEmpty(message "主鍵不能為空")PathVariable String[] YYIDs) {…