java实现ofd转pdf

提示:JAVA实现ofd转pdf、ofd转pdf时,中文识别失败的解决方案、ofd转pdf时,字体加粗的解决方案、本地ofd转换pdf正常,服务器中ofd转pdf中文失效的解决方案、代码里有字体文件,但是程序读取不到的解决方案、pdf转换时中文乱码问题解决

文章目录


简单说一下背景吧。我负责的机票业务线,要求开具电子行程单。但是由于供应商的不同,给提供的行程单也不同。有的能同时提供ofd和pdf(比如ibe+渠道),有的只提供ofd(比如易宝渠道),但是结算人员给客户提供的时候,需要同时提供ofd和pdf,这就需要研发人员手动生成pdf。

当然,xml、ofd是有电子签名的,pdf是没有电子签名的,我们也没法通过没电子签名的去生成有电子签名的文件,也就是说,我们可以通过xml/ofd去生成pdf,但是却不能反过来。

一、代码实现

1、pom依赖

复制代码
 <dependency>
            <groupId>org.ofdrw</groupId>
            <artifactId>ofdrw-full</artifactId>
            <version>2.3.3</version> <!-- 请检查最新版本 -->
        </dependency>

2、代码

复制代码
/**
     * ofd 转换为 pdf     无签名信息
     */
    @PostMapping("/ofdConverPdf")
    public Result ofdConverPdf(@RequestBody MailDto mail) throws Exception {

        String srcOfdPath = "D:\\ofdConverPdf\\a.ofd";
        String destPdfPath = "D:\\ofdConverPdf\\b.pdf"; // 最终PDF文件

        try {
            Path ofdFile = Paths.get(srcOfdPath);
            Path pdfFile = Paths.get(destPdfPath);
            ConvertHelper.toPdf(ofdFile, pdfFile); // 一步到位

            System.out.println("OFD转换PDF成功!文件: " + destPdfPath);
            // 请替换为你自己的成功返回方法
        } catch (GeneralConvertException e) {
            System.err.println("OFD转换PDF失败: " + e.getMessage());
            e.printStackTrace();
            return null;
        }

        return null;
    }

3、效果

转换前:

转换后:

pdf文件:

二、相关问题

问题的现象是,在我本地调用该方法,一切正常。部署到服务器后,转换出来的文件,要么是字体加粗了,要么是中文识别失败了,原因是我本地有字体文件,而linux服务器中没有相关的字体文件。

1.转换后的字体加粗

1.1、问题现象图

如下图所示,一些字体被加粗了

2.中文识别失败

2.1、问题现象图

一些字体识别失败了

代码如下(示例):

c 复制代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import  ssl
ssl._create_default_https_context = ssl._create_unverified_context

三、字体问题解决方案

定位到是字体因素导致的这些现象,我们就可以解决。方案1自然是服务器中加上相关字体,不过有时候会由于各种各样的因素,导致我们修改线上环境字体没那么方便,因此我们就着重介绍代码修复方式。

3.1、服务器加上相关字体

联系运维上传相关字体文件即可。或者可以找到对应的字体文件,自行上传,并刷新字体缓存即可

3.2、代码读取字体

这里需要注意,有时候由于字体的映射关系不一致,导致你即使再resource下加上了相关字体,并且引用了,他也读取不到,这就需要调试了。我下面贴的,亲测可用:

3.2.1、ofd转换pdf代码

c 复制代码
public String getPdfFileWithOfdFile(String ofdUrl) {
        String result = "";
        try {
            // 将ofd文件转换为pdf文件
            if (!ofdUrl.endsWith(".ofd")){
                return result;
            }
            Path tempOfdFile = null;
            Path tempPdfFile = null;
            String fileName = "电子行程单"+UUID.randomUUID();
            try {
                log.info("准备开始加载本地字体 flag{} {} ",fontsLoaded,ofdUrl);
                // 加载本地字体文件
                loadLocalFonts();
                log.info("开始加载本地字体结束 flag{} {}",fontsLoaded,ofdUrl);
                tempOfdFile = Files.createTempFile("temp_ofd", ".ofd");
                try (InputStream in = new URL(ofdUrl).openStream()) {
                    Files.copy(in, tempOfdFile, StandardCopyOption.REPLACE_EXISTING);
                }
                tempPdfFile = Files.createTempFile("temp_pdf", ".pdf");
                // 执行OFD到PDF转换
                ConvertHelper.toPdf(tempOfdFile, tempPdfFile);
                byte[] pdfBytes = Files.readAllBytes(tempPdfFile);
                result = PdfUtil.upload(pdfBytes, fileName, "pdf");
            }catch (Exception e){
                log.error("getPdfFileWithOfdFile###error",e);
            }finally {
                // 清理临时文件
                try {
                    if (tempOfdFile != null){
                        Files.deleteIfExists(tempOfdFile);
                        log.info("getPdfFileWithOfdFile 删除临时文件成功tempOfdFile");
                    }
                    if (tempPdfFile != null){
                        log.info("getPdfFileWithOfdFile 删除临时文件成功tempPdfFile");
                        Files.deleteIfExists(tempPdfFile);
                    }
                } catch (Exception e) {
                    log.error("getPdfFileWithOfdFile 清理临时文件失败: " , e);
                }
            }

        }catch (Exception e){
            log.error("getPdfFileWithOfdFile error",e);
        }
        return result;
    }

3.2.2、加载字体代码

复制代码
import org.ofdrw.converter.ConvertHelper;
import org.ofdrw.converter.FontLoader;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeUtility;
import javax.mail.search.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


   private static volatile boolean fontsLoaded = false;

private void loadLocalFonts() {
        FontLoader fontLoader = FontLoader.getInstance();
        try {
            log.info("loadLocalFontsStart{}",fontsLoaded);
            if (fontsLoaded){
                return;
            }
            synchronized (FONT_LOAD_LOCK){
                if (fontsLoaded){
                    return;
                }
                // 1) 固定从 classpath 的 resources/fonts 加载核心字体
                Map<String, String> ttf2Family = coreTtf2Family();
                java.util.Set<String> loaded = loadFontsFromClasspathDir(fontLoader, ttf2Family);
                if (loaded.isEmpty()) {
                    log.error("未加载到任何字体,请确认 resources/fonts 是否打包");
                    return;
                }

                // 2) 角色分配(按是否已加载) -> 使用字体家族名
                String primary = loaded.contains("simsun.ttf") ? ttf2Family.get("simsun.ttf") : null;         // 宋体
                String title = loaded.contains("simhei.ttf") ? ttf2Family.get("simhei.ttf") : null;           // 黑体
                String modern = loaded.contains("msyh.ttf") ? ttf2Family.get("msyh.ttf") : null;              // 微软雅黑
                String decor = loaded.contains("stkaiti.ttf") ? ttf2Family.get("stkaiti.ttf") : null;         // 楷体
                String monospace = loaded.contains("simfang.ttf") ? ttf2Family.get("simfang.ttf") : null;     // 仿宋

                String defaultFontName = primary != null ? primary : (title != null ? title : (modern != null ? modern : ttf2Family.get(loaded.iterator().next())));
                log.info("字体角色 - 宋体: {}, 黑体: {}, 微软雅黑: {}, 楷体: {}, 仿宋: {}", primary, title, modern, decor, monospace);
                log.info("默认字体: {}", defaultFontName);

                // 3) 应用映射(目标均为家族名,避免 ofdrw 无法内嵌字体)
                applyFontMappings(fontLoader, defaultFontName, primary, title, modern, decor, monospace);
                fontsLoaded = true;
                log.info("字体加载完成,默认字体策略: {} ,fontsLoaded {}", defaultFontName,fontsLoaded);
            }
        } catch (Exception e) {
            log.error("loadLocalFonts 加载字体失败", e);
        }
    }

    // 固定核心字体 TTF → 家族名
    private Map<String, String> coreTtf2Family() {
        Map<String, String> ttf2Family = new HashMap<>();
        ttf2Family.put("simsun.ttf", "SimSun");
        ttf2Family.put("simhei.ttf", "SimHei");
        ttf2Family.put("msyh.ttf", "Microsoft YaHei");
        ttf2Family.put("stkaiti.ttf", "STKaiti");
        ttf2Family.put("simfang.ttf", "FangSong");
        return ttf2Family;
    }

    // 从 classpath: fonts 目录加载字体为临时文件,再交由 FontLoader 加载
    private java.util.Set<String> loadFontsFromClasspathDir(FontLoader fontLoader, Map<String, String> ttf2Family) {
        java.util.Set<String> loaded = new java.util.LinkedHashSet<>();
        for (String ttf : ttf2Family.keySet()) {
            try (InputStream fontStream = getClass().getClassLoader().getResourceAsStream("fonts/" + ttf)) {
                if (fontStream == null) {
                    log.warn("未找到字体文件: fonts/{}", ttf);
                    continue;
                }
                Path tempFontFile = Files.createTempFile("ofdrw_font_", "_" + ttf);
                Files.copy(fontStream, tempFontFile, StandardCopyOption.REPLACE_EXISTING);
                try { tempFontFile.toFile().deleteOnExit(); } catch (Exception ignore) {}
                fontLoader.loadFont(tempFontFile);
                loaded.add(ttf);
                log.info("已加载字体: {} -> {}", ttf, ttf2Family.get(ttf));
            } catch (Exception ex) {
                log.warn("加载字体失败 {}: {}", ttf, ex.getMessage());
            }
        }
        return loaded;
    }

    // 按家族名设置映射,包含常见中文/西文字体与兜底
    private void applyFontMappings(FontLoader fontLoader,
                                   String defaultFontName,
                                   String primary, String title, String modern,
                                   String decor, String monospace) {
        // 先配置具体族,再加兜底,避免被 .* 覆盖
        if (primary != null) {
            fontLoader.addSimilarFontReplaceRegexMapping("^宋体$", primary);
            fontLoader.addSimilarFontReplaceRegexMapping(".*宋体.*", primary);
            fontLoader.addSimilarFontReplaceRegexMapping(".*SimSun.*", primary);
            fontLoader.addSimilarFontReplaceRegexMapping(".*NSimSun.*", primary);
            fontLoader.addSimilarFontReplaceRegexMapping(".*simsun.*", primary);
        }
        if (title != null) {
            fontLoader.addSimilarFontReplaceRegexMapping("^黑体$", title);
            fontLoader.addSimilarFontReplaceRegexMapping(".*黑体.*", title);
            fontLoader.addSimilarFontReplaceRegexMapping(".*SimHei.*", title);
            fontLoader.addSimilarFontReplaceRegexMapping(".*simhei.*", title);
            fontLoader.addSimilarFontReplaceRegexMapping(".*heiti.*", title);
        }
        if (modern != null) {
            fontLoader.addSimilarFontReplaceRegexMapping("^微软雅黑$", modern);
            fontLoader.addSimilarFontReplaceRegexMapping(".*微软雅黑.*", modern);
            fontLoader.addSimilarFontReplaceRegexMapping(".*Microsoft YaHei.*", modern);
            fontLoader.addSimilarFontReplaceRegexMapping(".*msyh.*", modern);
            fontLoader.addSimilarFontReplaceRegexMapping(".*yahei.*", modern);
        }
        // 楷体(若不存在则回退默认)
        String kaiti = decor != null ? decor : defaultFontName;
        fontLoader.addSimilarFontReplaceRegexMapping("^楷体$", kaiti);
        fontLoader.addSimilarFontReplaceRegexMapping("^KaiTi$", kaiti);
        fontLoader.addSimilarFontReplaceRegexMapping("^STKaiti$", kaiti);
        fontLoader.addSimilarFontReplaceRegexMapping("^楷体_GB2312$", kaiti);
        fontLoader.addSimilarFontReplaceRegexMapping(".*楷体.*", kaiti);
        fontLoader.addSimilarFontReplaceRegexMapping(".*楷书.*", kaiti);
        fontLoader.addSimilarFontReplaceRegexMapping(".*华文楷体.*", kaiti);
        fontLoader.addSimilarFontReplaceRegexMapping(".*方正楷体.*", kaiti);

        if (monospace != null) {
            fontLoader.addSimilarFontReplaceRegexMapping("^仿宋$", monospace);
            fontLoader.addSimilarFontReplaceRegexMapping(".*仿宋.*", monospace);
            fontLoader.addSimilarFontReplaceRegexMapping(".*FangSong.*", monospace);
            fontLoader.addSimilarFontReplaceRegexMapping(".*FangSong_GB2312.*", monospace);
        } else {
            fontLoader.addSimilarFontReplaceRegexMapping(".*仿宋.*", defaultFontName);
            fontLoader.addSimilarFontReplaceRegexMapping(".*FangSong.*", defaultFontName);
        }

        // 西文字体常见族
        String westModern = modern != null ? modern : defaultFontName;
        fontLoader.addSimilarFontReplaceRegexMapping(".*Arial.*", westModern);
        fontLoader.addSimilarFontReplaceRegexMapping(".*Helvetica.*", westModern);
        fontLoader.addSimilarFontReplaceRegexMapping(".*Calibri.*", westModern);
        String westFormal = primary != null ? primary : defaultFontName;
        fontLoader.addSimilarFontReplaceRegexMapping(".*Times.*", westFormal);
        fontLoader.addSimilarFontReplaceRegexMapping(".*times.*", westFormal);
        String westMono = monospace != null ? monospace : defaultFontName;
        fontLoader.addSimilarFontReplaceRegexMapping(".*Courier.*", westMono);
        fontLoader.addSimilarFontReplaceRegexMapping(".*courier.*", westMono);

        // 最后兜底:任何未匹配的字体使用默认字体
        fontLoader.addSimilarFontReplaceRegexMapping(".*", defaultFontName);
    }

总结

有一说一,这个字体真是废了我半天劲,明明识别并读取到字体文件了,转换完的pdf还是乱码,最后还是手动加的映射关系才转换成功了。在这里分享一下,大家遇到类似的问题,直接cv大法就行,省的耽误时间捣鼓这些细节了。我们工作中总是会遇到各种各样的问题,就比如只有ofd没有pdf(但是结算和运营需要同时有pdf和ofd才能给客户邮寄),遇到坑,我们分享出来,下一个人就不会同一个问题耽误很长时间了。

相关推荐
道可到3 小时前
阿里面试原题 java面试直接过06 | 集合底层——HashMap、ConcurrentHashMap、CopyOnWriteArrayList
java·后端·面试
狼爷3 小时前
从面试翻车到原理吃透:详解 synchronized 锁不住 Integer 的真相
java
麦麦鸡腿堡3 小时前
Java的封装
java·开发语言
wearegogog1233 小时前
Centos7下docker的jenkins下载并配置jdk与maven
java·docker·jenkins
青云交3 小时前
Java 大视界 -- Java 大数据在智能家居设备联动与场景自动化中的应用
java·大数据·智能家居·数据采集·设备联动·场景自动化·逻辑实现
野犬寒鸦3 小时前
从零起步学习MySQL || 第一章:初识MySQL及深入理解内部数据类型
java·服务器·数据库·后端·mysql
自由的疯4 小时前
java spring blob 附件 下载
java·后端·架构
vir024 小时前
翻转后1的数量(dp)
java·数据结构·算法
Full Stack Developme4 小时前
Python Calendar 模块教程
java·服务器·python