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

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

简介

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

Maven依赖

<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下无中文字体),中文字符无法正常显示,实际显示的是□□□,以下代码是最初的版本:

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源代码(非实际代码,仅介绍大概的实现逻辑)

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

修改后的代码

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. 对于碰到的问题,首先要确定导致问题发生的原因,应采取多次试错的方法,再考虑对应的解决方案,这样才能复用到下一次的问题解决当中。

http://www.kler.cn/news/302946.html

相关文章:

  • RP2040 C SDK clocks时钟源配置使用
  • 【Kubernetes】K8s 的鉴权管理(二):基于属性 / 节点 / Webhook 的访问控制
  • 《PhysDiff: Physics-Guided Human Motion Diffusion Model》ICCV2023
  • Rust使用Actix-web和SeaORM库开发WebAPI通过Swagger UI查看接口文档
  • 若依框架使用MyBatis-Plus中的baseMapper的方法报错Invalid bound statement (not found):
  • 中电金信:金融级数字底座“源启”:打造新型数字基础设施 筑牢千行百业数字化转型发展基石
  • sponge创建的服务与dtm连接使用etcd、consul、nacos进行服务注册与发现
  • GPT-4与ChatGPT:人工智能对话的新时代【含国内可用gpt】
  • 红帽7—tomcat的部署方法
  • Unity3D Android多渠道极速打包方案详解
  • [000-01-008].第05节:OpenFeign高级特性-请求/响应压缩
  • 【油猴脚本】00003案例 Tampermonkey油猴脚本引入css 库,油猴脚本css库的使用
  • web基础之RCE
  • Ansible简单部署与使用
  • Debian项目实战——环境搭建篇
  • ctfshow-web入门-sql注入(web244-web247)error 报错注入
  • java项目之基于Spring Boot智能无人仓库管理源码(springboot+vue)
  • 《JavaEE进阶》----14.<SpringMVC配置文件实践之【验证码项目】>
  • 【聊聊AI编程必不可少的NLTK及其punkt、punkt_tab安装】
  • Python | Leetcode Python题解之第395题至少有K个重复字符的最长子串
  • WRF-LES与PALM微尺度气象大涡模拟、PALM静态数据预备、PALM驱动数据预报、PALM模拟
  • 软件交付文档
  • NAND NOR FLASH闪存产品的学习记录
  • 充电桩平台的优惠券功能如何设计
  • 【编程底层原理】Tomcat为何要打破双亲委派模式
  • 布局管理, 分割窗口, 停靠窗口, 堆栈窗口, 综合应用
  • 代码随想录算法训练营第14天|226. 翻转二叉树、101. 对称二叉树、104. 二叉树的最大深度、111. 二叉树的最小深度
  • 基于Java的建筑节能监测系统+公共建筑能耗监测系统
  • 【笔记】1.1 拉伸力-伸长(延伸)曲线和应力-应变曲线
  • QT使用相机拍照