From 1a615c9374be53ec490cc3f795000d09346e104a Mon Sep 17 00:00:00 2001 From: aipper Date: Tue, 28 Oct 2025 16:26:20 +0800 Subject: [PATCH] todog --- .../strategy/common/OfdFontInspector.java | 138 ++++++++++++++++++ .../point/strategy/common/OfdToPdfUtil.java | 76 +++++----- 2 files changed, 179 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/point/strategy/common/OfdFontInspector.java diff --git a/src/main/java/com/point/strategy/common/OfdFontInspector.java b/src/main/java/com/point/strategy/common/OfdFontInspector.java new file mode 100644 index 0000000..5459dee --- /dev/null +++ b/src/main/java/com/point/strategy/common/OfdFontInspector.java @@ -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 listFonts(String ofdPath) { + Set 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 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 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 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 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; + } +} diff --git a/src/main/java/com/point/strategy/common/OfdToPdfUtil.java b/src/main/java/com/point/strategy/common/OfdToPdfUtil.java index d0f394f..941bacc 100644 --- a/src/main/java/com/point/strategy/common/OfdToPdfUtil.java +++ b/src/main/java/com/point/strategy/common/OfdToPdfUtil.java @@ -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 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 imagePaths, String targetFilePath) { + private static void imagesToPdfPageByPage(List 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) {} + } } }