2024-11-24-word转pdf&水印
本文使用libreOffice实现word转pdf,使用pdfBox添加水印
开发环境
- springboot 2.0.3.RELEASE
- libreOffice(需外置,单独安装):7.5.3.2 https://downloadarchive.documentfoundation.org/libreoffice/old/7.5.3.2/
- pdfBox:2.0.24
一、引入相关包
<!-- 集成libreOffice -->
<dependency>
<groupId>org.jodconverter</groupId>
<artifactId>jodconverter-local</artifactId>
<version>4.4.2</version>
</dependency>
<dependency>
<groupId>org.jodconverter</groupId>
<artifactId>jodconverter-core</artifactId>
<version>4.4.2</version>
</dependency>
<dependency>
<groupId>org.jodconverter</groupId>
<artifactId>jodconverter-spring-boot-starter</artifactId>
<version>4.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.24</version>
</dependency>
二、配置文件
- 提供libreOffice相关配置
- 参照以下配置,启动springboot服务的同时会自动启动系统中已安装的libreOffice,运行端口为2002
# libreOffice配置
jodconverter:
# 本地方式连接
local:
enabled: true
#安装路径
office-home: C:\Program Files (x86)\LibreOffice
#端口
portNumbers: 2002
#最大处理数量
maxTasksPerProcess: 100
# 任务执行的超时时间
task-execution-timeout: 360000
# 任务队列的超时时间
task-queue-timeout: 360000
# 一个进程的超时时间
process-timeout: 360000
三、编写工具类
- word2Pdf工具类
import com.hymake.framework.core.utils.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.jodconverter.core.DocumentConverter;
import org.jodconverter.core.document.DefaultDocumentFormatRegistry;
import org.jodconverter.local.LocalConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.UUID;
/**
* @Author: huanghwh
* @Date: 2024/11/01 下午8:46
* <p>
* word转pdf工具
*/
@Component
@Slf4j
public class WordPdfUtil {
private static boolean license = false;
/**
* 导出根路径
*/
private static String OUT_FOLDER_PATH;
@Value("${spring.web.upload.folder}")
public void setOutFolderPath(String outFolderPath) {
WordPdfUtil.OUT_FOLDER_PATH = outFolderPath;
}
/**
* doc转pdf
*
* @param wordPath word路径
* @param pdfPath pdf文件输出路径
* @return {@link File}
* @author huanghwh
* @date 2022/7/7 下午3:28
*/
public static File doc2Pdf(String wordPath, String pdfPath) {
return doc2Pdf(wordPath, pdfPath, false, null);
}
/**
* doc2 pdf doc转pdf
*
* @param wordPath word路径
* @param pdfPath pdf文件输出路径
* @param addWatermark 是否添加水印
* @param watermarkImgPath 水印img路径
* @return {@link File}
* @author huanghwh
* @date 2022/7/7 下午3:28
*/
public static File doc2Pdf(String wordPath, String pdfPath, boolean addWatermark, String watermarkImgPath) {
return doc2Pdf(wordPath, pdfPath, addWatermark, watermarkImgPath, false);
}
/**
* doc转pdf (libreOffice + pdfBox)
*
* @param wordPath word路径
* @param pdfPath pdf文件输出路径
* @param addWatermark 是否添加水印
* @param watermarkImgPath 水印图片路径
* @param removeBlank 是否删除空白页面 (当前版本不支持)
* @return {@link File}
* @author huanghwh
* @date 2024/11/01 下午3:28
*/
public static File doc2Pdf(String wordPath, String pdfPath, boolean addWatermark, String watermarkImgPath, boolean removeBlank) {
pdfPath = OUT_FOLDER_PATH + pdfPath + "/" + UUID.randomUUID() + ".pdf";
File inputFile = new File(wordPath);
File outputFile = new File(pdfPath);
try {
long old = System.currentTimeMillis();
DocumentConverter documentConverter = SpringContextUtils.getBean(LocalConverter.class);
documentConverter.convert(inputFile).to(outputFile).as(DefaultDocumentFormatRegistry.PDF).execute();
if (addWatermark) {
WatermarkUtil.insertWatermarkImgByPdfBox(outputFile, watermarkImgPath, 600, 600);
}
long now = System.currentTimeMillis();
log.info("Word 转 Pdf 共耗时:" + ((now - old) / 1000.0) + "秒");
return outputFile;
} catch (Exception e) {
e.printStackTrace();
ErrorUtils.printErrorLog(e);
throw new RuntimeException("Word 转 Pdf 失败: " + e.getMessage());
}
}
}
- 水印工具类
@Slf4j
public class WatermarkUtil {
/**
* 添加水印(pdfBox)
*
* @param pdfFile
* @param imgPath
* @param width
* @param height
* @return
* @author huanghwh
* @date 2024/11/1 下午4:08
*/
public static void insertWatermarkImgByPdfBox(File pdfFile, String imgPath, Integer width, Integer height) throws Exception {
log.info("开始添加水印");
// 下载并处理水印图像
File watermarkImage = downloadImage(imgPath);
try (PDDocument document = PDDocument.load(pdfFile)) {
// 创建 PDFBox 图像对象
PDImageXObject pdImage = PDImageXObject.createFromFile(watermarkImage.getAbsolutePath(), document);
// 在每一页添加水印
for (PDPage page : document.getPages()) {
try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.PREPEND, true, true)) {
// 保存当前图形状态
contentStream.saveGraphicsState();
// 计算水印的位置
float x = (page.getMediaBox().getWidth() - width) / 2; // 水平居中
float y = (page.getMediaBox().getHeight() - height) / 2; // 垂直居中
// 添加水印图像
contentStream.drawImage(pdImage, x, y, width, height);
// 恢复图形状态
contentStream.restoreGraphicsState();
}
}
document.save(pdfFile);
log.info("水印添加完毕");
} catch (Exception e) {
ErrorUtils.printErrorLog(e);
throw new CommonException("添加水印失败:" + e.toString());
} finally {
if (watermarkImage.exists()) {
boolean deleted = watermarkImage.delete();
if (!deleted) {
log.warn("未能删除水印文件: " + watermarkImage.getAbsolutePath());
}
}
}
}
/**
* 下载水印文件
*
* @param imageUrl
* @return
* @author huanghwh
* @date 2024/11/1 下午4:09
*/
private static File downloadImage(String imageUrl) throws IOException {
URL url = new URL(imageUrl);
// 临时文件
File tempFile = File.createTempFile("watermark", ".png");
try (InputStream in = url.openStream(); FileOutputStream out = new FileOutputStream(tempFile)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
return tempFile;
}
}
三、使用示例
String outPath = "/project/";
String templateName = "demo.docx";
File docFile = PoiTlUtil.exportWord(templateName, dataMap, outPath);
// doc加水印并转成pdf
Params waterMarkParam = paramsService.getByParamCode("WATER_MARK_IMG");
// waterMarkParam.getParamValue() 为水印远程地址,例如:http://192.168.0.1:8085/static/watermark.png
File pdfFile = WordPdfUtil.doc2Pdf(docFile.getPath(), outPath, true, waterMarkParam.getParamValue(), true);
四、使用问题
1.转pdf后排版发生变化
- 字体不存在时,libreOffice会使用替代字体,造成排版变化,尝试修改字体,或将需要的字体放置在操作系统的字体文件夹下,libreOffice将读取系统字体。