|
@@ -6,9 +6,12 @@ import shop.alien.entity.store.vo.StoreOperationalStatisticsComparisonVo;
|
|
|
import javax.imageio.ImageIO;
|
|
import javax.imageio.ImageIO;
|
|
|
import java.awt.*;
|
|
import java.awt.*;
|
|
|
import java.awt.image.BufferedImage;
|
|
import java.awt.image.BufferedImage;
|
|
|
|
|
+import java.io.ByteArrayInputStream;
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
|
|
|
+import java.io.File;
|
|
|
import java.io.InputStream;
|
|
import java.io.InputStream;
|
|
|
import java.math.BigDecimal;
|
|
import java.math.BigDecimal;
|
|
|
|
|
+import java.nio.file.Files;
|
|
|
import java.text.DecimalFormat;
|
|
import java.text.DecimalFormat;
|
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
@@ -32,8 +35,13 @@ public class StatisticsComparisonImageUtil {
|
|
|
private static final Color SECTION_TITLE_COLOR = new Color(33, 33, 33); // #212121
|
|
private static final Color SECTION_TITLE_COLOR = new Color(33, 33, 33); // #212121
|
|
|
private static final Color BORDER_COLOR = new Color(224, 224, 224); // #E0E0E0
|
|
private static final Color BORDER_COLOR = new Color(224, 224, 224); // #E0E0E0
|
|
|
|
|
|
|
|
- // 字体:优先 classpath 内嵌字体(Linux 无微软雅黑时避免中文方框),其次系统支持中文的字体
|
|
|
|
|
- private static final String FONT_CLASSPATH = "fonts/NotoSansSC-Regular.ttf";
|
|
|
|
|
|
|
+ // 字体:优先 classpath 内 fonts/ 下的字体文件(与 resources/fonts/ 对应),支持 .ttf / .otf
|
|
|
|
|
+ private static final String[] FONT_RESOURCE_PATHS = {
|
|
|
|
|
+ "fonts/NotoSansSC-Regular.ttf",
|
|
|
|
|
+ "fonts/NotoSansCJKsc-Regular.otf",
|
|
|
|
|
+ "/fonts/NotoSansSC-Regular.ttf",
|
|
|
|
|
+ "/fonts/NotoSansCJKsc-Regular.otf"
|
|
|
|
|
+ };
|
|
|
private static final String[] FONT_FALLBACK_NAMES = {
|
|
private static final String[] FONT_FALLBACK_NAMES = {
|
|
|
"Microsoft YaHei",
|
|
"Microsoft YaHei",
|
|
|
"PingFang SC",
|
|
"PingFang SC",
|
|
@@ -64,10 +72,28 @@ public class StatisticsComparisonImageUtil {
|
|
|
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#,##0.00");
|
|
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#,##0.00");
|
|
|
private static final DecimalFormat PERCENT_FORMAT = new DecimalFormat("#,##0.00%");
|
|
private static final DecimalFormat PERCENT_FORMAT = new DecimalFormat("#,##0.00%");
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 获取支持中文的字体(用于绘制报表,避免 Linux 下无微软雅黑导致中文方框)
|
|
|
|
|
- * 优先:classpath 下 fonts/NotoSansSC-Regular.ttf;其次系统已安装的中文字体名
|
|
|
|
|
- */
|
|
|
|
|
|
|
+ /** 从 classpath 读取字体资源为字节数组(JAR 内更可靠) */
|
|
|
|
|
+ private static byte[] readFontBytes(String path) {
|
|
|
|
|
+ ClassLoader cl = StatisticsComparisonImageUtil.class.getClassLoader();
|
|
|
|
|
+ for (String p : new String[]{path, path.startsWith("/") ? path.substring(1) : "/" + path}) {
|
|
|
|
|
+ InputStream is = cl.getResourceAsStream(p);
|
|
|
|
|
+ if (is == null && Thread.currentThread().getContextClassLoader() != null) {
|
|
|
|
|
+ is = Thread.currentThread().getContextClassLoader().getResourceAsStream(p);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (is == null) continue;
|
|
|
|
|
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
|
|
|
|
+ byte[] buf = new byte[8192];
|
|
|
|
|
+ int n;
|
|
|
|
|
+ while ((n = is.read(buf)) != -1) out.write(buf, 0, n);
|
|
|
|
|
+ return out.toByteArray();
|
|
|
|
|
+ } catch (Exception ignored) {
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ try { is.close(); } catch (Exception ignored) {}
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private static Font getChineseFont(int style, int size) {
|
|
private static Font getChineseFont(int style, int size) {
|
|
|
if (baseChineseFont != null) {
|
|
if (baseChineseFont != null) {
|
|
|
return baseChineseFont.deriveFont(style, size);
|
|
return baseChineseFont.deriveFont(style, size);
|
|
@@ -76,49 +102,46 @@ public class StatisticsComparisonImageUtil {
|
|
|
return new Font(chineseFontName, style, size);
|
|
return new Font(chineseFontName, style, size);
|
|
|
}
|
|
}
|
|
|
synchronized (StatisticsComparisonImageUtil.class) {
|
|
synchronized (StatisticsComparisonImageUtil.class) {
|
|
|
- if (baseChineseFont != null) {
|
|
|
|
|
- return baseChineseFont.deriveFont(style, size);
|
|
|
|
|
- }
|
|
|
|
|
- if (chineseFontName != null) {
|
|
|
|
|
- return new Font(chineseFontName, style, size);
|
|
|
|
|
- }
|
|
|
|
|
- try {
|
|
|
|
|
- InputStream is = StatisticsComparisonImageUtil.class.getClassLoader().getResourceAsStream(FONT_CLASSPATH);
|
|
|
|
|
- if (is != null) {
|
|
|
|
|
- baseChineseFont = Font.createFont(Font.TRUETYPE_FONT, is).deriveFont(Font.PLAIN, 14);
|
|
|
|
|
- is.close();
|
|
|
|
|
- log.info("使用内嵌中文字体: {}", FONT_CLASSPATH);
|
|
|
|
|
|
|
+ if (baseChineseFont != null) return baseChineseFont.deriveFont(style, size);
|
|
|
|
|
+ if (chineseFontName != null) return new Font(chineseFontName, style, size);
|
|
|
|
|
+ for (String path : FONT_RESOURCE_PATHS) {
|
|
|
|
|
+ byte[] bytes = readFontBytes(path);
|
|
|
|
|
+ if (bytes == null || bytes.length == 0) continue;
|
|
|
|
|
+ try {
|
|
|
|
|
+ baseChineseFont = Font.createFont(Font.TRUETYPE_FONT, new ByteArrayInputStream(bytes)).deriveFont(Font.PLAIN, 14f);
|
|
|
return baseChineseFont.deriveFont(style, size);
|
|
return baseChineseFont.deriveFont(style, size);
|
|
|
|
|
+ } catch (Exception e1) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ File tmp = File.createTempFile("noto_", path.endsWith(".otf") ? ".otf" : ".ttf");
|
|
|
|
|
+ Files.write(tmp.toPath(), bytes);
|
|
|
|
|
+ baseChineseFont = Font.createFont(Font.TRUETYPE_FONT, tmp).deriveFont(Font.PLAIN, 14f);
|
|
|
|
|
+ tmp.delete();
|
|
|
|
|
+ return baseChineseFont.deriveFont(style, size);
|
|
|
|
|
+ } catch (Exception e2) {
|
|
|
|
|
+ // skip
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.warn("加载内嵌中文字体失败,将尝试系统字体: {}", e.getMessage());
|
|
|
|
|
}
|
|
}
|
|
|
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
|
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
|
|
String[] names = ge.getAvailableFontFamilyNames();
|
|
String[] names = ge.getAvailableFontFamilyNames();
|
|
|
if (names != null) {
|
|
if (names != null) {
|
|
|
for (String candidate : FONT_FALLBACK_NAMES) {
|
|
for (String candidate : FONT_FALLBACK_NAMES) {
|
|
|
for (String name : names) {
|
|
for (String name : names) {
|
|
|
- if (candidate.equals(name)) {
|
|
|
|
|
- Font f = new Font(name, Font.PLAIN, 14);
|
|
|
|
|
- if (f.canDisplayUpTo("经营数据") < 0) {
|
|
|
|
|
- chineseFontName = name;
|
|
|
|
|
- log.info("使用系统中文字体: {}", name);
|
|
|
|
|
- return new Font(chineseFontName, style, size);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!candidate.equals(name)) continue;
|
|
|
|
|
+ Font f = new Font(name, Font.PLAIN, 14);
|
|
|
|
|
+ if (f.canDisplayUpTo("经") >= 0) continue;
|
|
|
|
|
+ chineseFontName = name;
|
|
|
|
|
+ return new Font(chineseFontName, style, size);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
for (String name : names) {
|
|
for (String name : names) {
|
|
|
Font f = new Font(name, Font.PLAIN, 14);
|
|
Font f = new Font(name, Font.PLAIN, 14);
|
|
|
- if (f.canDisplayUpTo("经") < 0) {
|
|
|
|
|
- chineseFontName = name;
|
|
|
|
|
- log.info("使用系统中文字体(自动): {}", name);
|
|
|
|
|
- return new Font(chineseFontName, style, size);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (f.canDisplayUpTo("经") >= 0) continue;
|
|
|
|
|
+ chineseFontName = name;
|
|
|
|
|
+ return new Font(chineseFontName, style, size);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
chineseFontName = Font.SANS_SERIF;
|
|
chineseFontName = Font.SANS_SERIF;
|
|
|
- log.warn("未找到支持中文的字体,使用 SansSerif,Linux 下中文可能显示为方框,建议在 resources/fonts/ 放入 NotoSansSC-Regular.ttf");
|
|
|
|
|
return new Font(chineseFontName, style, size);
|
|
return new Font(chineseFontName, style, size);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|