java实现ofd转pdf
提示:JAVA实现ofd转pdf、ofd转pdf时,中文识别失败的解决方案、ofd转pdf时,字体加粗的解决方案、本地ofd转换pdf正常,服务器中ofd转pdf中文失效的解决方案、代码里有字体文件,但是程序读取不到的解决方案、pdf转换时中文乱码问题解决
文章目录
- 一、代码实现
- 1、pom依赖
- 2、代码
- 3、效果
- 二、相关问题
- 1.转换后的字体加粗
- 1.1、问题现象图
- 2.中文识别失败
- 2.1、问题现象图
- 三、字体问题解决方案
- 3.1、服务器加上相关字体
- 3.2、代码读取字体
- 3.2.1、ofd转换pdf代码
- 3.2.2、加载字体代码
- 总结
简单说一下背景吧。我负责的机票业务线,要求开具电子行程单。但是由于供应商的不同,给提供的行程单也不同。有的能同时提供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、问题现象图
一些字体识别失败了
代码如下(示例):
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代码
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才能给客户邮寄),遇到坑,我们分享出来,下一个人就不会同一个问题耽误很长时间了。