SpringBoot项目用Aspose-Words将Word转换为PDF文件正常显示中文的正确姿势

简介

目前需要实现将Word转换为PDF的功能,但在实现过程中遇到了一个问题:生成的PDF文件中的中文变成了方框。

Maven依赖

xml 复制代码
<dependency>
    <groupId>com.aspose</groupId>
    <artifactId>aspose-words</artifactId>
    <version>20.9</version>
    <scope>compile</scope>
</dependency>
<dependency>
	<groupId>cn.afterturn</groupId>
	<artifactId>easypoi-spring-boot-starter</artifactId>
	<version>4.4.0</version>
</dependency>

源代码

起初,是希望使用aspose-words依赖将Word转换为PDF文件,但是如果Linux中未安装字体库(字体默认路径:/usr/share/fonts下无中文字体),中文字符无法正常显示,实际显示的是□□□,以下代码是最初的版本:

java 复制代码
import cn.hutool.system.SystemUtil;
import com.aspose.words.Document;
import com.aspose.words.FontSettings;
import com.aspose.words.PdfSaveOptions;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class ExcelHandlerTest {
    /**
     * word转换为pdf
     *
     * @param docx docx
     * @throws Exception
     */
    public OutputStream wordToPdf(XWPFDocument docx) throws Exception {
        InputStream wordInput = null;
        OutputStream pdfOutput = new ByteArrayOutputStream();
        try {
            wordInput = convertToInputStream(docx);
            //通过aspose-words中的类转换文件
            Document wordDoc = new Document(wordInput);
            //判断当前是否为Linux系统
            String osName = SystemUtil.getOsInfo().getName();
            boolean isLinux = osName.toLowerCase().contains("linux");
            if (isLinux) {
                FontSettings settings = new FontSettings();
                //设置汉字字体,否则转换后的文档汉字显示异常。
                settings.setFontsFolder("/usr/share/fonts", false);
                wordDoc.setFontSettings(settings);
            }
            PdfSaveOptions pso = new PdfSaveOptions();
            wordDoc.save(pdfOutput, pso);
        } catch (Exception e) {
            throw new RuntimeException("导出PDF异常");
        } finally {
            if (wordInput != null) {
                wordInput.close();
            }
            if (pdfOutput != null) {
                pdfOutput.close();
            }
        }
        return pdfOutput;
    }

    public static InputStream convertToInputStream(XWPFDocument doc) throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // 写入文档到输出流
        doc.write(out);
        // 将输出流转换为字节数组
        byte[] docBytes = out.toByteArray();
        // 关闭输出流
        out.close();
        // 将字节数组转换为输入流
        return new ByteArrayInputStream(docBytes);
    }
}

用以上代码处理后,则出现了使用本地运行和线上运行结果不一致的问题,本地PDF能够正常显示,而线上导出的中文均显示为方框□□□。

经过排查,最后得出了结论:因为本地是通过windows系统启动,默认安装了一部分中文字体库,线上则是Linux系统,并没有安装对应的中文字体库,因此才导致线上中文显示有误的问题。

解决方案

  1. 直接在Linux中安装所需字体库(可安装在字体库默认地址下,也可在其他路径下),此方式无需修改代码。
  2. 字体库在项目下的resources文件夹下(.setFontsFolder方法需获取字体库的绝对路径,无法直接通过resources路径获取,但可先将resources下字体文件转成流,再创建临时文件进行处理),此方式需修改代码。

Aspose-Words源代码(非实际代码,仅介绍大概的实现逻辑)

java 复制代码
public static String[] handler(String var0) {
	//这里是通过new File()方式读取字体库文件
	File[] var1 = (new File(var0)).listFiles();
	
	//具体实现逻辑......
}

修改后的代码

java 复制代码
package com.baseus.finance.infrastructure.utils;

import com.aspose.words.Document;
import com.aspose.words.FontSettings;
import com.aspose.words.SaveFormat;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.stereotype.Component;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.HashSet;
import java.util.Set;

@Component
public class ExcelHandlerTest {

    private static final Set<String> REQUIRED_FONTS = new HashSet<>();

    static {
        // 添加其他必要的字体文件
        REQUIRED_FONTS.add("msyhl.ttc");
        REQUIRED_FONTS.add("msyhbd.ttc");
        REQUIRED_FONTS.add("msyh.ttc");
    }

    private static final Path FONT_CACHE_DIR = Paths.get(System.getProperty("java.io.tmpdir"), "fontCache");

    /**
     * word转换为pdf
     * @param docx docx
     * @throws Exception
     */
    public OutputStream wordToPdf(XWPFDocument docx) throws Exception {
        // 创建一个临时文件来保存XWPFDocument
        File tempFile = null;
        try {
            tempFile = File.createTempFile(String.valueOf(SnowFlakeUtil.nextId()), ".docx");
            try (FileOutputStream fos = new FileOutputStream(tempFile)) {
             	// 将XWPFDocument写入临时文件
                docx.write(fos);
            }

            // 使用Aspose.Words加载临时文件
            Document wordDoc = new Document(tempFile.getPath());
            // 设置字体目录,确保Aspose.Words能够找到中文字体
            FontSettings fontSettings = new FontSettings();
            // 确保字体缓存目录存在
            if (!Files.exists(FONT_CACHE_DIR)) {
                Files.createDirectories(FONT_CACHE_DIR);
            }

            // 复制必要的字体文件到缓存目录
            copyResourceFontsToCacheDir();
            fontSettings.setFontsFolder(FONT_CACHE_DIR.toString(), true);
            wordDoc.setFontSettings(fontSettings);
            // 使用ByteArrayOutputStream来捕获PDF输出
            ByteArrayOutputStream pdfOutput = new ByteArrayOutputStream();

            // 将Aspose.Words的Document保存为PDF
            wordDoc.save(pdfOutput, SaveFormat.PDF);

            // 返回PDF内容的字节数组
            return pdfOutput;
        } finally {
            // 清理:删除临时文件
            if (tempFile != null && tempFile.exists()) {
                tempFile.delete();
            }
        }
    }

    private void copyResourceFontsToCacheDir() throws Exception {
        for (String fontFile : REQUIRED_FONTS) {
            Path fontPath = FONT_CACHE_DIR.resolve(fontFile);
            if (!Files.exists(fontPath)) {
                try (InputStream fontStream = getClass().getClassLoader().getResourceAsStream("fonts/" + fontFile)) {
                    if (fontStream == null) {
                        throw new IllegalArgumentException("Font file not found: " + fontFile);
                    }
                    Files.copy(fontStream, fontPath, StandardCopyOption.REPLACE_EXISTING);
                }
            }
        }
    }
}

总结

  1. 考虑到如果选择第一种方式会增加运维难度(部署不同的服务器都需要安装对应的中文字体库),最终方案选择了第二种。
  2. 在使用第二种解决方案之前,本来是考虑直接通过获取resources下的字体库的方式,但项目是jar包方式启动,获取到的字体库的地址并不是一个文件位置,因此才考虑使用文件流的方式进行处理。
  3. 对于碰到的问题,首先要确定导致问题发生的原因,应采取多次试错的方法,再考虑对应的解决方案,这样才能复用到下一次的问题解决当中。
相关推荐
Kali_075 分钟前
使用 Mathematical_Expression 从零开始实现数学题目的作答小游戏【可复制代码】
java·人工智能·免费
rzl0217 分钟前
java web5(黑马)
java·开发语言·前端
君爱学习23 分钟前
RocketMQ延迟消息是如何实现的?
后端
超喜欢下雨天26 分钟前
服务器安装 ros2时遇到底层库依赖冲突的问题
linux·运维·服务器·ros2
guojl37 分钟前
深度解读jdk8 HashMap设计与源码
java
Falling4240 分钟前
使用 CNB 构建并部署maven项目
后端
guojl42 分钟前
深度解读jdk8 ConcurrentHashMap设计与源码
java
程序员小假1 小时前
我们来讲一讲 ConcurrentHashMap
后端
爱上语文1 小时前
Redis基础(5):Redis的Java客户端
java·开发语言·数据库·redis·后端