基于libreoffice实现的文档转换项目,无框架依赖,即插即用
- 1. 技术栈
- 2. 功能
- 3. 使用
- 4. 待办事项
- 5. 注意事项
- 6. 参考链接
-
LibreOffice:v6.2.3
-
jodconverter:4.2.2
-
PDFBox:2.0.12
-
cglib动态代理 + 懒汉工厂模式 + 策略模式 + 装饰器模式
-
qtools-property管理配置文件(applicatpwdion.yml、bootstrap.yml、workable-converter.yml三种命名的配置文件任意包含一种即可)
-
支持doc、docx、html、ppt、png、pdf等等类型的文件互相转换
-
支持按照文件路径、字节输入输出流、Base64等不同姿势转换
-
不依赖第三方框架,即插即用,支持application.yml、bootstrap.yml、workable-converter.yml三种配置(自己项目中具体配置一个即可)
CentOS请直接参考这篇文章:CentOS7安装LibreOffice6.2.3
windows跟Mac同样可以在上述文章中拿到下载链接
安装完成后,请记住您的LibreOffice的Home目录,后面需要用到
默认目录:
-
CentOS: /opt/libreoffice6.2/
-
Mac: /Applications/LibreOffice.app/Contents/
-
Windows: C:\Program Files\LibreOffice\
- Maven
<dependency>
<groupId>com.liumapp.workable.converter</groupId>
<artifactId>workable-converter</artifactId>
<version>v1.4.2</version>
</dependency>
- Gradle
compile group: 'com.liumapp.workable.converter', name: 'workable-converter', version: 'v1.2.0'
在项目的resources目录下,创建一个yml配置文件,需要确保文件名称为application.yml、bootstrap.yml或workable-converter.yml三种命名任意一个即可
添加以下配置:
com:
liumapp:
workable-converter:
libreofficePath: "/Applications/LibreOffice.app/Contents"
libreofficePath的值为LibreOffice:6.2.3的安装目录
完整的配置项列表如下
参数名 | 解释 | 默认值 |
---|---|---|
libreofficePath | LibreOffice安装目录 | (String) 无默认值,该项必填 |
libreofficePort | LibreOffice监听端口 | (int) 2002 |
tmpPath | 临时存储目录 | (String) "./data/" |
以doc转PDF为例
WorkableConverter converter = new WorkableConverter();//实例化的同时,初始化配置项,配置项的校验通过Decorator装饰
ConvertPattern pattern = ConvertPatternManager.getInstance();
pattern.fileToFile("./data/test.doc", "./data/pdf/result1.pdf"); //test.doc为待转换文件路径,result1.pdf为转换结果存储路径
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.DOC);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);
converter.setConverterType(CommonConverterManager.getInstance());//策略模式,后续实现了新的转换策略后,在此处更换,图片转换将考虑使用新的策略来完成
boolean result = converter.convert(pattern.getParameter();
如果要用html转PDF,将上述代码的
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.DOC);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);
改为
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.HTML);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);
其他类型的同理
以doc转pdf为例
// you can also choice not use proxy
WorkableConverter converter = new WorkableConverter();
ConvertPattern pattern = ConvertPatternManager.getInstance();
pattern.streamToStream(new FileInputStream("./data/test.doc"), new FileOutputStream("./data/pdf/result1_2.pdf"));
// attention !!! convert by stream must set prefix.
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.DOC);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);
converter.setConverterType(CommonConverterManager.getInstance());
boolean result = converter.convert(pattern.getParameter();
跟上例基本相同,唯一的变化是通过pattern.streamToStream()来设置输入输出流,转换源文件数据从输入流中读取,转换结果会直接写入输出流中,
同时要切换转换格式,跟上例一样设置不同的prefix即可
仍以doc转pdf为例
WorkableConverter converter = new WorkableConverter();
ConvertPattern pattern = ConvertPatternManager.getInstance();
pattern.base64ToBase64(Base64FileTool.FileToBase64(new File("./data/test.doc")));
// attention !!! convert by base64 must set prefix.
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.DOC);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);
converter.setConverterType(CommonConverterManager.getInstance());
boolean result = converter.convert(pattern.getParameter();
String destBase64 = pattern.getBase64Result();
输入base64执行转换,首先通过pattern.base64ToBase64()来设置转换源的base64值
转换结果result仍然是一个boolean类型,通过pattern.getBase64Result来获取转换结果的base64值
要切换转换格式,跟上例一样设置不同的prefix即可
目前对于图片的处理,只支持将PDF转PNG图片(如果1份pdf文件有20页,那么将会转换为20张png图片),该功能的实现基于PDFBox:2.0.12
pattern.fileToFiles()第一个参数为待转换的pdf文件路径,第二个参数为转换后的图片存储路径
WorkableConverter converter = new WorkableConverter();
ConvertPattern pattern = ConvertPatternManager.getInstance();
pattern.fileToFiles("./data/test5.pdf", "./data/");
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PNG);
converter.setConverterType(PdfBoxConverterManager.getInstance()); // pdf box converter manager only support pdf to png
assertEquals(true, converter.convert(pattern.getParameter()));
assertEquals(true, FileTool.isFileExists("./data/test5_0.png"));
assertEquals(true, FileTool.isFileExists("./data/test5_1.png"));
assertEquals(true, FileTool.isFileExists("./data/test5_2.png"));
assertEquals(true, FileTool.isFileExists("./data/test5_3.png"));
pattern.base64ToBase64()的参数为待转换pdf文件的base64值
转换结束后,通过List<String> resultBase64 = pattern.getBase64Results()
获取转换后的图片base64值的集合
WorkableConverter converter = new WorkableConverter();
ConvertPattern pattern = ConvertPatternManager.getInstance();
pattern.base64ToBase64(Base64FileTool.FileToBase64(new File("./data/test5.pdf")));
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PNG);
converter.setConverterType(PdfBoxConverterManager.getInstance()); // pdf box converter manager only support pdf to png
boolean result = converter.convert(pattern.getParameter());
List<String> resultBase64 = pattern.getBase64Results();
assertEquals(true, result);
assertEquals(4, resultBase64.size());
水印的转换策略为WaterMarkConverter
添加水印注意事项
-
请确保输入源文件后缀为PDF,输出源文件后缀也为PDF
-
水印参数需要new一个WaterMarkRequire来设置
-
setWaterMarkPage(int page)代表在哪一页上添加水印,如果为0,则表示所有页面
-
水印本身为一个PDF文件,该文件只需要一页,其第一页的内容将被视为水印添加到源文件中
比如说,要添加透明度为0.3的文本作为水印的话,自己使用word等工具绘制透明度为0.3的字体(或者上包含透明度的png图片也可以)并另存为一个watermark.pdf文件
然后使用waterMarkRequire.setWaterMarkPDFBase64(Base64FileTool.FileToBase64(new File("./data/watermark.pdf")))
或者waterMarkRequire.setWaterMarkPDFBytes(FileUtils.readFileToByteArray(new File("./data/watermark.pdf")))将该文件的base64或者bytes值输入即可
具体使用可以分为三种方式
WorkableConverter converter = new WorkableConverter();
converter.setConverterType(WaterMarkConverterManager.getInstance());//选择具体的水印转换策略
ConvertPattern pattern = ConvertPatternManager.getInstance();
WaterMarkRequire waterMarkRequire = new WaterMarkRequire();//创建水印所需要的参数
//指定在具体的哪一页添加水印,0的话则在所有页面添加水印
waterMarkRequire.setWaterMarkPage(0);//0 means all age
waterMarkRequire.setWaterMarkPDFBase64(Base64FileTool.FileToBase64(new File("./data/watermark.pdf")));
pattern.setWaterMarkRequire(waterMarkRequire);
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.fileToFile("./data/test5.pdf", "./data/test5_with_mark01.pdf");//添加水印后的文件保存在./data/目录下,名为test5_with_mark01.pdf
boolean result = converter.convert(pattern.getParameter());
assertEquals(true, result);
WorkableConverter converter = new WorkableConverter();
converter.setConverterType(WaterMarkConverterManager.getInstance());
ConvertPattern pattern = ConvertPatternManager.getInstance();
WaterMarkRequire waterMarkRequire = new WaterMarkRequire();
waterMarkRequire.setWaterMarkPage(0);//0 means all age
waterMarkRequire.setWaterMarkPDFBytes(FileUtils.readFileToByteArray(new File("./data/watermark.pdf")));
pattern.setWaterMarkRequire(waterMarkRequire);
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.streamToStream(new FileInputStream("./data/test5.pdf"), new FileOutputStream("./data/test5_with_mark02.pdf"));
boolean result = converter.convert(pattern.getParameter());
assertEquals(true, result);
WorkableConverter converter = new WorkableConverter();
converter.setConverterType(WaterMarkConverterManager.getInstance());
ConvertPattern pattern = ConvertPatternManager.getInstance();
WaterMarkRequire waterMarkRequire = new WaterMarkRequire();
waterMarkRequire.setWaterMarkPage(0);//0 means all age
waterMarkRequire.setWaterMarkPDFBase64(Base64FileTool.FileToBase64(new File("./data/watermark.pdf")));
pattern.setWaterMarkRequire(waterMarkRequire);
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.base64ToBase64(Base64FileTool.FileToBase64(new File("./data/test5.pdf")));
boolean result = converter.convert(pattern.getParameter());
String base64Result = pattern.getBase64Result();
Base64FileTool.saveBase64File(base64Result, "./data/test5_with_mark03.pdf");
assertEquals(true, result);
itext7编辑pdf策略为TextConverter
编辑pdf注意事项
-
请在配置文件添加 com.liumapp.workable-converter=fontsPath: 字体文件目录,resources文件下有所需字体文件
-
请确保输入源文件后缀为PDF,输出源文件后缀也为PDF
-
编辑pdf参数需要new一个TextRequire来设置
WorkableConverter converter = new WorkableConverter();
converter.setConverterType(TextConverterManager.getInstance());
ConvertPattern pattern = ConvertPatternManager.getInstance();
TextRequire templateRequire = new TextRequire();
List<PdfEditDTO> pdfEditDTOList = new ArrayList<>();
PdfEditDTO pdfEditDTO = new PdfEditDTO();
pdfEditDTO.setFieldName("one");
pdfEditDTO.setFontSize(18);
pdfEditDTO.setFontType(FontType.SIMYOU);
pdfEditDTO.setPositionX(0f);
pdfEditDTO.setPositionY(10f);
pdfEditDTO.setText("Hello World I am One Field");
pdfEditDTO.setWidth(250);
pdfEditDTO.setHeitht(20);
pdfEditDTO.setPageNum(1);
pdfEditDTOList.add(pdfEditDTO);
PdfEditDTO pdfEditDTO2 = new PdfEditDTO();
pdfEditDTO2.setFieldName("two");
pdfEditDTO2.setFontSize(18);
pdfEditDTO2.setFontType(FontType.STKAITI);
pdfEditDTO2.setPositionX(0f);
pdfEditDTO2.setPositionY(820f);
pdfEditDTO2.setText("Hello World I am Two Field");
pdfEditDTO2.setWidth(250);
pdfEditDTO2.setHeitht(20);
pdfEditDTO2.setPageNum(2);
pdfEditDTOList.add(pdfEditDTO2);
templateRequire.setPdfEditDTOList(pdfEditDTOList);
pattern.setTextRequire(templateRequire);
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.fileToFile("./data/txt.pdf","./data/txt2.pdf");
boolean result = converter.convert(pattern.getParameter());
assertEquals(true, result);
WorkableConverter converter = new WorkableConverter();
converter.setConverterType(TextConverterManager.getInstance());
ConvertPattern pattern = ConvertPatternManager.getInstance();
TextRequire templateRequire = new TextRequire();
List<PdfEditDTO> pdfEditDTOList = new ArrayList<>();
PdfEditDTO pdfEditDTO = new PdfEditDTO();
pdfEditDTO.setFieldName("one");
pdfEditDTO.setFontSize(18);
pdfEditDTO.setFontType(FontType.SIMYOU);
pdfEditDTO.setPositionX(0f);
pdfEditDTO.setPositionY(10f);
pdfEditDTO.setText("Hello World I am One Field");
pdfEditDTO.setWidth(250);
pdfEditDTO.setHeitht(20);
pdfEditDTO.setPageNum(1);
pdfEditDTOList.add(pdfEditDTO);
PdfEditDTO pdfEditDTO2 = new PdfEditDTO();
pdfEditDTO2.setFieldName("two");
pdfEditDTO2.setFontSize(18);
pdfEditDTO2.setFontType(FontType.STKAITI);
pdfEditDTO2.setPositionX(0f);
pdfEditDTO2.setPositionY(820f);
pdfEditDTO2.setText("Hello World I am Two Field");
pdfEditDTO2.setWidth(250);
pdfEditDTO2.setHeitht(20);
pdfEditDTO2.setPageNum(2);
pdfEditDTOList.add(pdfEditDTO2);
templateRequire.setPdfEditDTOList(pdfEditDTOList);
pattern.setTextRequire(templateRequire);
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);
pattern.base64ToBase64(Base64FileTool.FileToBase64(new File("./data/txt.pdf")));
boolean result = converter.convert(pattern.getParameter());
String base64Result = pattern.getBase64Result();
Base64FileTool.saveBase64File(base64Result,"./data/txt2.pdf");
增加ItextConverter策略;
public class ItextConverterTest {
@Test
public void testItextConverter() throws ConvertFailedException, IOException {
WorkableConverter converter = new WorkableConverter();
converter.setConverterType(HtmlToPdfByItextConverterManager.getInstance());
ConvertPattern pattern = ConvertPatternManager.getInstance();
EditorRequire editorRequire = new EditorRequire();
editorRequire.setContent("<html><div>你好,word</div></html>");
pattern.setEditorRequire(editorRequire);
pattern.setSrcFilePrefix(DefaultDocumentFormatRegistry.HTML);
pattern.setDestFilePrefix(DefaultDocumentFormatRegistry.PDF);
String base64 = Base64.getEncoder().encodeToString(editorRequire.getContent().getBytes());
pattern.base64ToBase64(base64);
assertEquals(true,converter.convert(pattern.getParameter()));
String base64Result = pattern.getBase64Result();
Base64FileTool.saveBase64File(base64Result,"./data/test2.pdf");
}
}
参数介绍:
EditorRequire: 在这个对象中的content
为html内容;
base64Result: 为转换结果的PDF文件内容的base64值;
-
已经测试通过的有doc、docx、html 按照不同姿势转PDF,其他类型的并没有编写测试单元,后续考虑增加
-
目前只支持yml配置,后续考虑添加其他类型的配置支持(xml、properties等)
-
目前Markdown格式很流行,考虑实现markdown格式的字符串转PDF(markdown -> html -> pdf)
-
因为需要LibreOffice的支持,所以不建议在Docker等容器内运行(LibreOffice暂无Docker稳定发行版的镜像)
-
转换乱码、转换耗时过长,请检查服务器是否安装有中文字体
-
项目启动后,在执行第一次转换任务时,因为涉及到与LibreOffice建立连接等操作,所以会耗时较长,第二次任务及以后稳定在0.5秒以内(具体时间因机器配置会有所差异)