当前位置: 首页 > news >正文

java 富文本转pdf(支持水印)

前言:

本文的目的是将传入的富文本内容(html标签,图片)并且分页导出为pdf。

所用的核心依赖为iText7。

因为itextpdf-core的核心包在maven中央仓库中,阿里云华为云等拉不下来,中央仓库在外网,并且此包在中央仓库中未提供可下载的jar包文件,所以通过iText github上提供的core包的组成jar,实现core包所需方法的调用。

且支持自定义水印

一、jar包导入

在子项目的resources下新建lib目录,下载文件所提供的压缩包,解压jar包文件到lib下,如图所示

二、maven依赖导入

        <dependency><groupId>com.itextpdf</groupId><artifactId>barcodes</artifactId><version>7.2.3</version><scope>system</scope><systemPath>${project.basedir}/src/main/resources/lib/barcodes-7.2.3.jar</systemPath></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>commons</artifactId><version>7.2.3</version><scope>system</scope><systemPath>${project.basedir}/src/main/resources/lib/commons-7.2.3.jar</systemPath></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>font-asian</artifactId><version>7.2.3</version><scope>system</scope><systemPath>${project.basedir}/src/main/resources/lib/font-asian-7.2.3.jar</systemPath></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>forms</artifactId><version>7.2.3</version><scope>system</scope><systemPath>${project.basedir}/src/main/resources/lib/forms-7.2.3.jar</systemPath></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>hyph</artifactId><version>7.2.3</version><scope>system</scope><systemPath>${project.basedir}/src/main/resources/lib/hyph-7.2.3.jar</systemPath></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>io</artifactId><version>7.2.3</version><scope>system</scope><systemPath>${project.basedir}/src/main/resources/lib/io-7.2.3.jar</systemPath></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>kernel</artifactId><version>7.2.3</version><scope>system</scope><systemPath>${project.basedir}/src/main/resources/lib/kernel-7.2.3.jar</systemPath></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>layout</artifactId><version>7.2.3</version> <!-- 如果需要布局功能 --><scope>system</scope><systemPath>${project.basedir}/src/main/resources/lib/layout-7.2.3.jar</systemPath></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>pdfa</artifactId><version>7.2.3</version><scope>system</scope><systemPath>${project.basedir}/src/main/resources/lib/pdfa-7.2.3.jar</systemPath></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>pdftest</artifactId><version>7.2.3</version><scope>system</scope><systemPath>${project.basedir}/src/main/resources/lib/pdftest-7.2.3.jar</systemPath></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>sign</artifactId><version>7.2.3</version><scope>system</scope><systemPath>${project.basedir}/src/main/resources/lib/sign-7.2.3.jar</systemPath></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>styled-xml-parser</artifactId><version>7.2.3</version><scope>system</scope><systemPath>${project.basedir}/src/main/resources/lib/styled-xml-parser-7.2.3.jar</systemPath></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>svg</artifactId><version>7.2.3</version><scope>system</scope><systemPath>${project.basedir}/src/main/resources/lib/svg-7.2.3.jar</systemPath></dependency><!-- https://mvnrepository.com/artifact/com.itextpdf/html2pdf --><dependency><groupId>com.itextpdf</groupId><artifactId>html2pdf</artifactId><version>4.0.3</version></dependency>

三、打包配置

因为从外部引入jar包,在本地测试没有问题,但是打包后发布,引用不了,所以需要配置打包引用外部jar包。所以可以根据以下链接,博主的另外一个文章提供了相关的方法。

java引用第三方jar包,打包全流程_java引入外部jar包-CSDN博客

四、工具类

package cn.aotu.sss.module.sss.util.text.excel;import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.html2pdf.resolver.font.DefaultFontProvider;
import com.itextpdf.io.exceptions.IOException;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.io.font.constants.StandardFonts;
import com.itextpdf.io.source.ByteArrayOutputStream;
import com.itextpdf.kernel.colors.DeviceGray;
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.font.FontProvider;
import com.itextpdf.layout.properties.TextAlignment;
import com.itextpdf.layout.properties.VerticalAlignment;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;@Component
public class RichTextExporter {/*** HTML内容转PDF并输出到响应流* @param htmlContent HTML内容* @param response HttpServletResponse* @param request HttpServletRequest(用于获取基础路径)* @param fileName 生成的PDF文件名* @throws IOException 转换过程异常*/public static void convertToPdf(String htmlContent,HttpServletResponse response,HttpServletRequest request,String fileName,String watermarkText) throws IOException, java.io.IOException {// 配置响应头configureResponseHeaders(response, fileName, request);try (OutputStream outputStream = response.getOutputStream()) {// 创建转换器配置ConverterProperties converterProperties = createConverterProperties(request);// 创建PDF文档ByteArrayOutputStream baos = new ByteArrayOutputStream();PdfWriter writer = new PdfWriter(baos);PdfDocument pdf = new PdfDocument(writer);// 判断水印文本是否为空if (watermarkText != null && !watermarkText.isEmpty()) {// 添加水印事件监听器WatermarkEventHandler watermarkHandler = new WatermarkEventHandler(watermarkText);pdf.addEventHandler(PdfDocumentEvent.END_PAGE, watermarkHandler);}// 执行转换Document document = HtmlConverter.convertToDocument(htmlContent, pdf, converterProperties);document.close();// 将生成的PDF输出到响应流outputStream.write(baos.toByteArray());}}/*** 配置HTTP响应头*/private static void configureResponseHeaders(HttpServletResponse response,String fileName,HttpServletRequest request) {response.setContentType("application/pdf");String attachment = String.format("attachment; filename=\"%s\"",encodeFileName(fileName, request));response.setHeader("Content-Disposition", attachment);response.setCharacterEncoding(StandardCharsets.UTF_8.name());}/*** 创建转换器配置(处理路径、字体、字符集等)*/private static ConverterProperties createConverterProperties(HttpServletRequest request) {ConverterProperties properties = new ConverterProperties();// 设置基础URI(解析相对路径,如图片/字体地址)properties.setBaseUri(getBaseHttpPath(request));// 配置字体(支持中文、非嵌入字体、符号)FontProvider fontProvider = new DefaultFontProvider(true, true, true);properties.setFontProvider(fontProvider);// 设置字符集properties.setCharset(StandardCharsets.UTF_8.name());return properties;}/*** 获取完整的基础HTTP路径(包含协议、域名、端口、上下文路径)*/private static String getBaseHttpPath(HttpServletRequest request) {int port = request.getServerPort();String portStr = (port == 80 || port == 443) ? "" : ":" + port;return String.format("%s://%s%s%s",request.getScheme(),request.getServerName(),portStr,request.getContextPath());}/*** 处理文件名编码(兼容不同浏览器)*/private static String encodeFileName(String fileName, HttpServletRequest request) {String userAgent = request.getHeader("User-Agent");try {if (userAgent.contains("MSIE") || userAgent.contains("Edge") || userAgent.contains("Chrome")) {return java.net.URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");} else {return new String(fileName.getBytes(StandardCharsets.UTF_8), "ISO-8859-1");}} catch (Exception e) {return fileName;}}/*** 水印事件监听器*/static class WatermarkEventHandler implements IEventHandler {private final String watermarkText;private final PdfFont font;public WatermarkEventHandler(String watermarkText) throws java.io.IOException {this.watermarkText = watermarkText;// 使用正确的方法重载,第三个参数为 EmbeddingStrategy 枚举(推荐嵌入策略)this.font = PdfFontFactory.createFont("/static/fonts/STSongStd-Light.ttf",  // 引用字体配置文件PdfEncodings.IDENTITY_H,  // 使用标准编码PdfFontFactory.EmbeddingStrategy.FORCE_EMBEDDED  // 优先嵌入字体,确保跨设备显示);}@Overridepublic 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.newContentStreamBefore(), page.getResources(), pdf);// 修改此处,使用正确的构造函数Canvas canvas = new Canvas(pdfCanvas, pageSize);// 将角度转换为弧度float radAngle = (float) (45 * Math.PI / 180);int pageNumber = pdf.getPageNumber(page);//            // 设置单个水印
//            canvas.setFont(font)
//                    .setFontSize(80) //字体大小
//                    .setFontColor(DeviceGray.GRAY) // 水印颜色
//                    .setOpacity(0.4f) // 透明的, 0-1 越大越不透明
//                    .showTextAligned(
//                            new Paragraph(watermarkText),
//                            pageSize.getWidth() / 2,
//                            pageSize.getHeight() / 2,
//                            pageNumber,
//                            TextAlignment.CENTER,
//                            VerticalAlignment.MIDDLE,
//                            radAngle
//                    );// 每页设置多个水印(3行,每行5个)drawWatermarks(canvas, pageSize, pageNumber, radAngle, watermarkText,font);canvas.close();}}private static void drawWatermarks(Canvas canvas, Rectangle pageSize, int pageNumber, float radAngle, String watermarkText, PdfFont font) {int rows = 3; // 水印每页有多少行int cols = 5; // 水印每行有多少个// 计算水印在水平和垂直方向的间隔float xInterval = pageSize.getWidth() / (cols + 1);float yInterval = pageSize.getHeight() / (rows + 1);for (int i = 1; i <= rows; i++) {for (int j = 1; j <= cols; j++) {float x = j * xInterval;float y = i * yInterval;canvas.setFont(font).setFontSize(10) // 可根据实际情况调整字体大小.setFontColor(DeviceGray.GRAY).setOpacity(0.4f) // 可根据实际情况调整透明度.showTextAligned(new Paragraph(watermarkText),x,y,pageNumber,TextAlignment.CENTER,VerticalAlignment.MIDDLE,radAngle);}}}}

五、controller

    @PostMapping("/export-pdf")@Operation(summary = "导出pdf")@Parameters({@Parameter(name = "htmlContent", description = "富文本内容", required = true),@Parameter(name = "watermarkText", description = "水印内容", required = false)})@Parameter(name = "htmlContent", description = "富文本内容", required = true)public void exportPdf(@RequestParam(value = "htmlContent", required = true) String htmlContent,@RequestParam(value = "watermarkText", required = false)  String watermarkText,HttpServletRequest request,HttpServletResponse response) throws IOException {RichTextExporter.convertToPdf(htmlContent, response, request, "pdf导出测试", watermarkText);}

 六、水印字体的设置

水印字体的配置文件,放到项目的路径下,可自由更改字体配置文件的位置。

修改PdfFontFactory.createFont中的第一个传参即可

ttl字体文件已提供,由于每篇文章仅能引用一个资源,请大家移步博主另外一偏文章获取ttl文件:

Java 富文本转word-CSDN博客

七、yaml配置

因为富文本内容很大,所以如果必须用post请求,并且需要修改上传配置,在yaml文件中加入如下配置即可:

###设置缓冲区大小,方便上传大文件
server:tomcat:max-swallow-size: 15MBmax-http-form-post-size: 16777216

八、测试

九、注意

1、标签

经测试,富文本内容最好把ol标签给过滤掉,因为会导致富文本导出为pdf出错,并且如果还需要支持其余特殊的标签,请自行扩展工具类。

2、水印配置

水印参数非必传,支持中文/英文的水印内容。

若需要修改水印配置,则修改drawWatermarks方法中的设置即可。

如下所示:

setFontSize 即设置水印字体大小

setFontColor 即设置水印字体的颜色

setOpacity 即设置水印字体的透明度,范围0-1,越小越透明

private static void drawWatermarks(Canvas canvas, Rectangle pageSize, int pageNumber, float radAngle, String watermarkText, PdfFont font) {int rows = 3; // 水印每页有多少行int cols = 5; // 水印每行有多少个// 计算水印在水平和垂直方向的间隔float xInterval = pageSize.getWidth() / (cols + 1);float yInterval = pageSize.getHeight() / (rows + 1);for (int i = 1; i <= rows; i++) {for (int j = 1; j <= cols; j++) {float x = j * xInterval;float y = i * yInterval;canvas.setFont(font).setFontSize(10) // 可根据实际情况调整字体大小.setFontColor(DeviceGray.GRAY).setOpacity(0.4f) // 可根据实际情况调整透明度.showTextAligned(new Paragraph(watermarkText),x,y,pageNumber,TextAlignment.CENTER,VerticalAlignment.MIDDLE,radAngle);}}}


http://www.mrgr.cn/news/100375.html

相关文章:

  • DeepSeek智能时空数据分析(三):专业级地理数据可视化赏析-《杭州市国土空间总体规划(2021-2035年)》
  • 桌面端开发技术栈选型:开启高效开发之旅
  • 线程池单例模式
  • gem5-gpu教程04 高速缓存一致性协议和缓存拓扑
  • (六)RestAPI 毛子(外部导入打卡/游标分页/内存缓存/批量提交/Quartz后台任务/Hateoas Driven)
  • DataStreamAPI实践原理——计算模型
  • 前端面试宝典---vue实现简化版
  • 上位机知识篇---寻址方式
  • 2025蓝桥省赛c++B组第二场题解
  • Spring Bean生命周期原理、常见的后处理器实现
  • C#中常见的设计模式
  • 【Linux应用】交叉编译环境配置,以及最简单粗暴的环境移植(直接从目标板上复制)
  • Unity接入火山引擎/豆包大模型文生图,实现AI绘画
  • WSL 安装过程整理
  • K8S Service 原理、案例
  • OpenCV --- 图像预处理(七)
  • Adruino:传感器及步进电机
  • 移动通信行业术语
  • 软件项目实施全流程及交付物清单
  • Android HAL HIDL