todog
This commit is contained in:
138
src/main/java/com/point/strategy/common/OfdFontInspector.java
Normal file
138
src/main/java/com/point/strategy/common/OfdFontInspector.java
Normal file
@@ -0,0 +1,138 @@
|
||||
package com.point.strategy.common;
|
||||
|
||||
import org.dom4j.Document;
|
||||
import org.dom4j.Element;
|
||||
import org.dom4j.io.SAXReader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
/**
|
||||
* OFD 字体扫描工具
|
||||
* 作用:解析 OFD 包/目录中的 XML,输出声明的字体(FontName/FamilyName/粗细等)
|
||||
* 注意:此工具列出“声明的字体”,未必都是“实际被使用”的字体;但可用于定位缺失的粗体/家族。
|
||||
*/
|
||||
public class OfdFontInspector {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(OfdFontInspector.class);
|
||||
|
||||
/**
|
||||
* 扫描 OFD 的字体声明并返回去重后的描述列表
|
||||
* @param ofdPath OFD 文件路径(.ofd 压缩包)或已解压目录
|
||||
* @return 字体信息列表(去重)
|
||||
*/
|
||||
public static List<String> listFonts(String ofdPath) {
|
||||
Set<String> fonts = new LinkedHashSet<>();
|
||||
try {
|
||||
Path p = Paths.get(ofdPath);
|
||||
if (Files.isDirectory(p)) {
|
||||
// 仅遍历 Res 目录下的 *Res.xml(PublicRes.xml/DocumentRes.xml/PageRes.xml),降低解析失败概率
|
||||
Files.walk(p)
|
||||
.filter(f -> f.toString().matches("(?i).*/Res/.*Res\\.xml$"))
|
||||
.forEach(xml -> {
|
||||
try (InputStream in = Files.newInputStream(xml)) {
|
||||
parseXml(fonts, in, xml.toString());
|
||||
} catch (Exception e) {
|
||||
log.debug("[OfdFontInspector] 打开失败 src={}, err={}", xml, e.toString());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 压缩包:遍历所有 .xml 条目
|
||||
try (ZipFile zf = new ZipFile(new File(ofdPath))) {
|
||||
Enumeration<? extends ZipEntry> entries = zf.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry e = entries.nextElement();
|
||||
String name = e.getName();
|
||||
if (!e.isDirectory() && name.matches("(?i).*/Res/.*Res\\.xml$")) {
|
||||
try (InputStream is = zf.getInputStream(e)) {
|
||||
parseXml(fonts, is, name);
|
||||
} catch (Exception ex) {
|
||||
log.debug("[OfdFontInspector] 打开失败 src={}, err={}", name, ex.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("扫描 OFD 字体失败: " + ex.getMessage(), ex);
|
||||
}
|
||||
return new ArrayList<>(fonts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印字体声明到控制台(便于快速定位)
|
||||
*/
|
||||
public static void printFonts(String ofdPath) {
|
||||
List<String> list = listFonts(ofdPath);
|
||||
if (list.isEmpty()) {
|
||||
log.info("[OfdFontInspector] 未在资源中发现字体声明。");
|
||||
} else {
|
||||
log.info("[OfdFontInspector] 发现字体声明共 {} 项:", list.size());
|
||||
for (String s : list) {
|
||||
log.info(" - {}", s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单个 XML 流,提取 Font 节点信息
|
||||
*/
|
||||
private static void parseXml(Set<String> out, InputStream is, String sourceName) {
|
||||
try {
|
||||
SAXReader reader = secureSAXReader();
|
||||
Document doc = reader.read(is);
|
||||
if (doc == null) return;
|
||||
Element root = doc.getRootElement();
|
||||
if (root == null) return;
|
||||
|
||||
// 使用 local-name() 规避命名空间差异,匹配 Font 节点
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Element> fontNodes = root.selectNodes("//*[local-name()='Font']");
|
||||
for (Element font : fontNodes) {
|
||||
// 常见属性名(OFD CT_Font):FontName / FamilyName / Bold / Italic / Weight 等
|
||||
String fontName = getAttr(font, "FontName");
|
||||
String family = getAttr(font, "FamilyName");
|
||||
String bold = getAttr(font, "Bold");
|
||||
String italic = getAttr(font, "Italic");
|
||||
String weight = getAttr(font, "Weight");
|
||||
String id = getAttr(font, "ID");
|
||||
|
||||
String desc = String.format(Locale.ROOT,
|
||||
"[src=%s] ID=%s FontName=%s Family=%s Bold=%s Italic=%s Weight=%s",
|
||||
sourceName, nullToEmpty(id), nullToEmpty(fontName), nullToEmpty(family),
|
||||
nullToEmpty(bold), nullToEmpty(italic), nullToEmpty(weight));
|
||||
out.add(desc);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// 单个 XML 解析失败不影响整体
|
||||
log.debug("[OfdFontInspector] 解析失败 src={}, err={}", sourceName, ex.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static String getAttr(Element e, String name) {
|
||||
return e.attributeValue(name);
|
||||
}
|
||||
|
||||
private static String nullToEmpty(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
// 关闭外部实体/DTD,避免解析失败和安全问题
|
||||
private static SAXReader secureSAXReader() throws Exception {
|
||||
SAXReader reader = new SAXReader(false);
|
||||
try { reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); } catch (Exception ignore) {}
|
||||
try { reader.setFeature("http://xml.org/sax/features/validation", false); } catch (Exception ignore) {}
|
||||
try { reader.setFeature("http://xml.org/sax/features/external-general-entities", false); } catch (Exception ignore) {}
|
||||
try { reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false); } catch (Exception ignore) {}
|
||||
return reader;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import com.spire.pdf.PdfDocument;
|
||||
import com.spire.pdf.graphics.PdfImage;
|
||||
// import removed: PdfSection not needed when按页缩放适配
|
||||
|
||||
/**
|
||||
* OFD转PDF工具类
|
||||
@@ -29,64 +30,69 @@ public class OfdToPdfUtil {
|
||||
Path ofdPath = Paths.get(resourceFilePath);
|
||||
Path tempDir = Paths.get(new File(targetFilePath).getParent(), "temp_" + System.currentTimeMillis());
|
||||
tempDir.toFile().mkdirs();
|
||||
|
||||
// 使用高质量设置将OFD转换为PNG图片
|
||||
|
||||
// 为提升整体速度改回 JPG,分辨率适中(10ppm≈254DPI)
|
||||
try (OFDReader reader = new OFDReader(ofdPath)) {
|
||||
ImageExporter exporter = new ImageExporter(ofdPath, tempDir, "PNG", 30.0);
|
||||
ImageExporter exporter = new ImageExporter(ofdPath, tempDir, "JPG", 10.0);
|
||||
exporter.export();
|
||||
exporter.close();
|
||||
|
||||
|
||||
List<Path> imagePaths = exporter.getImgFilePaths();
|
||||
exporter.close();
|
||||
if (imagePaths.isEmpty()) {
|
||||
throw new RuntimeException("未能从OFD文件中提取图片");
|
||||
}
|
||||
|
||||
// 将图片合并为PDF,保留颜色
|
||||
imagesToPdf(imagePaths, targetFilePath);
|
||||
|
||||
// 将图片分页写入PDF并及时清理临时图片,减少IO与峰值占用
|
||||
imagesToPdfPageByPage(imagePaths, targetFilePath);
|
||||
}
|
||||
|
||||
|
||||
// 清理临时文件
|
||||
deleteDirectory(tempDir.toFile());
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("OFD转PDF失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将图片列表合并为PDF,保留原始颜色
|
||||
* 分页处理图片列表合并为PDF,边写边清理,兼顾颜色与性能
|
||||
*/
|
||||
private static void imagesToPdf(List<Path> imagePaths, String targetFilePath) {
|
||||
private static void imagesToPdfPageByPage(List<Path> imagePaths, String targetFilePath) {
|
||||
PdfDocument pdf = null;
|
||||
try {
|
||||
PdfDocument pdf = new PdfDocument();
|
||||
|
||||
pdf = new PdfDocument();
|
||||
|
||||
for (Path imagePath : imagePaths) {
|
||||
// 使用Spire.PDF的PdfImage.fromFile方法直接从文件加载图片
|
||||
com.spire.pdf.graphics.PdfImage image = com.spire.pdf.graphics.PdfImage.fromFile(imagePath.toString());
|
||||
|
||||
if (image != null) {
|
||||
// 创建新页面,大小与图片相同
|
||||
com.spire.pdf.PdfPageBase page = pdf.getPages().add();
|
||||
|
||||
// 计算图片适配页面的缩放比例
|
||||
double widthFitRate = image.getPhysicalDimension().getWidth() / page.getCanvas().getClientSize().getWidth();
|
||||
double heightFitRate = image.getPhysicalDimension().getHeight() / page.getCanvas().getClientSize().getHeight();
|
||||
float fitRate = Math.max((float)widthFitRate, (float)heightFitRate);
|
||||
|
||||
// 计算适配后的尺寸
|
||||
double fitWidth = image.getPhysicalDimension().getWidth() / fitRate;
|
||||
double fitHeight = image.getPhysicalDimension().getHeight() / fitRate;
|
||||
|
||||
// 将图片绘制到页面,保留原始颜色
|
||||
page.getCanvas().drawImage(image, 0, 30, fitWidth, fitHeight);
|
||||
com.spire.pdf.graphics.PdfImage image = null;
|
||||
try {
|
||||
image = com.spire.pdf.graphics.PdfImage.fromFile(imagePath.toString());
|
||||
if (image != null) {
|
||||
// 使用默认页面尺寸,按比例缩放图片以适配页面
|
||||
com.spire.pdf.PdfPageBase page = pdf.getPages().add();
|
||||
|
||||
double widthFitRate = image.getPhysicalDimension().getWidth() / page.getCanvas().getClientSize().getWidth();
|
||||
double heightFitRate = image.getPhysicalDimension().getHeight() / page.getCanvas().getClientSize().getHeight();
|
||||
float fitRate = Math.max((float) widthFitRate, (float) heightFitRate);
|
||||
|
||||
double fitWidth = image.getPhysicalDimension().getWidth() / fitRate;
|
||||
double fitHeight = image.getPhysicalDimension().getHeight() / fitRate;
|
||||
|
||||
// 顶部预留少量边距,避免顶边遮挡
|
||||
page.getCanvas().drawImage(image, 0, 30, fitWidth, fitHeight);
|
||||
}
|
||||
} finally {
|
||||
// 及时清理磁盘临时图片
|
||||
try { java.nio.file.Files.deleteIfExists(imagePath); } catch (Exception ignore) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pdf.saveToFile(targetFilePath);
|
||||
pdf.close();
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("图片合并PDF失败: " + e.getMessage(), e);
|
||||
} finally {
|
||||
if (pdf != null) {
|
||||
try { pdf.close(); } catch (Exception ignore) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user