提示: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才能给客户邮寄),遇到坑,我们分享出来,下一个人就不会同一个问题耽误很长时间了。