|
|
|
@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
package org.dromara.common.word.pdf;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import com.lowagie.text.Font;
|
|
|
|
|
|
|
|
import com.lowagie.text.pdf.BaseFont;
|
|
|
|
|
|
|
|
import fr.opensagres.poi.xwpf.converter.pdf.PdfOptions;
|
|
|
|
|
|
|
|
import fr.opensagres.xdocreport.itext.extension.font.IFontProvider;
|
|
|
|
|
|
|
|
import fr.opensagres.xdocreport.itext.extension.font.ITextFontRegistry;
|
|
|
|
|
|
|
|
import lombok.AccessLevel;
|
|
|
|
|
|
|
|
import lombok.NoArgsConstructor;
|
|
|
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import java.awt.Color;
|
|
|
|
|
|
|
|
import java.io.InputStream;
|
|
|
|
|
|
|
|
import java.nio.file.Files;
|
|
|
|
|
|
|
|
import java.nio.file.Path;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Word 转 PDF 选项工厂(中文字体 + 编码)
|
|
|
|
|
|
|
|
* <p>
|
|
|
|
|
|
|
|
* 未配置中文字体时,PDF 常出现汉字宽度计算错误,导致文字与表格线重叠。
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
@Slf4j
|
|
|
|
|
|
|
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
|
|
|
|
|
|
|
public final class WordPdfOptionsFactory {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final String FONT_CACHE_KEY = "zh-cn";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final Map<String, BaseFont> BASE_FONT_CACHE = new ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 创建适用于中文文档的 PDF 转换选项
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public static PdfOptions createChinesePdfOptions() {
|
|
|
|
|
|
|
|
PdfOptions options = PdfOptions.create();
|
|
|
|
|
|
|
|
options.fontEncoding(BaseFont.IDENTITY_H);
|
|
|
|
|
|
|
|
options.fontProvider(chineseFontProvider());
|
|
|
|
|
|
|
|
return options;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static IFontProvider chineseFontProvider() {
|
|
|
|
|
|
|
|
return (familyName, encoding, size, style, color) -> {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
BaseFont baseFont = resolveChineseBaseFont();
|
|
|
|
|
|
|
|
Font font = new Font(baseFont, size, style, color == null ? Color.BLACK : color);
|
|
|
|
|
|
|
|
if (familyName != null) {
|
|
|
|
|
|
|
|
font.setFamily(familyName);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return font;
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
log.warn("加载中文字体失败,使用 PDF 默认字体, familyName={}", familyName, e);
|
|
|
|
|
|
|
|
return ITextFontRegistry.getRegistry().getFont(familyName, encoding, size, style, color);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static BaseFont resolveChineseBaseFont() throws Exception {
|
|
|
|
|
|
|
|
BaseFont cached = BASE_FONT_CACHE.get(FONT_CACHE_KEY);
|
|
|
|
|
|
|
|
if (cached != null) {
|
|
|
|
|
|
|
|
return cached;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
synchronized (WordPdfOptionsFactory.class) {
|
|
|
|
|
|
|
|
cached = BASE_FONT_CACHE.get(FONT_CACHE_KEY);
|
|
|
|
|
|
|
|
if (cached != null) {
|
|
|
|
|
|
|
|
return cached;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
BaseFont loaded = loadChineseBaseFont();
|
|
|
|
|
|
|
|
BASE_FONT_CACHE.put(FONT_CACHE_KEY, loaded);
|
|
|
|
|
|
|
|
log.info("PDF 导出已加载中文字体: {}", loaded.getPostscriptFontName());
|
|
|
|
|
|
|
|
return loaded;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static BaseFont loadChineseBaseFont() throws Exception {
|
|
|
|
|
|
|
|
// 1) classpath 内置字体(fonts/simsun.ttc、msyh.ttc 或 .ttf)
|
|
|
|
|
|
|
|
BaseFont classpathFont = loadClasspathFont("fonts/simsun.ttf", "simsun.ttf");
|
|
|
|
|
|
|
|
if (classpathFont != null) {
|
|
|
|
|
|
|
|
return classpathFont;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
classpathFont = loadClasspathFont("fonts/simsun.ttc", "simsun.ttc,0");
|
|
|
|
|
|
|
|
if (classpathFont != null) {
|
|
|
|
|
|
|
|
return classpathFont;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
classpathFont = loadClasspathFont("fonts/msyh.ttf", "msyh.ttf");
|
|
|
|
|
|
|
|
if (classpathFont != null) {
|
|
|
|
|
|
|
|
return classpathFont;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
classpathFont = loadClasspathFont("fonts/msyh.ttc", "msyh.ttc,0");
|
|
|
|
|
|
|
|
if (classpathFont != null) {
|
|
|
|
|
|
|
|
return classpathFont;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2) 操作系统字体(开发机 Windows / Linux 常见路径)
|
|
|
|
|
|
|
|
List<String> candidates = new ArrayList<>();
|
|
|
|
|
|
|
|
String os = System.getProperty("os.name", "").toLowerCase();
|
|
|
|
|
|
|
|
if (os.contains("win")) {
|
|
|
|
|
|
|
|
candidates.add("C:/Windows/Fonts/simsun.ttc,0");
|
|
|
|
|
|
|
|
candidates.add("C:/Windows/Fonts/msyh.ttc,0");
|
|
|
|
|
|
|
|
candidates.add("C:/Windows/Fonts/simhei.ttf");
|
|
|
|
|
|
|
|
candidates.add("C:/Windows/Fonts/arialuni.ttf");
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
candidates.add("/usr/share/fonts/truetype/wqy/wqy-microhei.ttc,0");
|
|
|
|
|
|
|
|
candidates.add("/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc,0");
|
|
|
|
|
|
|
|
candidates.add("/usr/share/fonts/truetype/arphic/uming.ttc,0");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (String fontPath : candidates) {
|
|
|
|
|
|
|
|
String filePart = fontPath.contains(",") ? fontPath.substring(0, fontPath.indexOf(',')) : fontPath;
|
|
|
|
|
|
|
|
if (Files.isRegularFile(Path.of(filePart))) {
|
|
|
|
|
|
|
|
return BaseFont.createFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
throw new IllegalStateException(
|
|
|
|
|
|
|
|
"未找到可用的中文字体,请在 ruoyi-common-word/src/main/resources/fonts/ 下放置 simsun.ttc/msyh.ttc(或 .ttf),"
|
|
|
|
|
|
|
|
+ "或在服务器安装中文字体");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static BaseFont loadClasspathFont(String resourcePath, String fontNameForCreate) throws Exception {
|
|
|
|
|
|
|
|
try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourcePath)) {
|
|
|
|
|
|
|
|
if (is == null) {
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return BaseFont.createFont(fontNameForCreate, BaseFont.IDENTITY_H, BaseFont.EMBEDDED, true,
|
|
|
|
|
|
|
|
is.readAllBytes(), null);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|