|
|
|
@ -3,6 +3,7 @@ package com.biutag.supervision.util; |
|
|
|
import org.jfree.chart.ChartFactory; |
|
|
|
import org.jfree.chart.ChartFactory; |
|
|
|
import org.jfree.chart.ChartUtils; |
|
|
|
import org.jfree.chart.ChartUtils; |
|
|
|
import org.jfree.chart.JFreeChart; |
|
|
|
import org.jfree.chart.JFreeChart; |
|
|
|
|
|
|
|
import org.jfree.chart.block.BlockBorder; |
|
|
|
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator; |
|
|
|
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator; |
|
|
|
import org.jfree.chart.labels.StandardPieSectionLabelGenerator; |
|
|
|
import org.jfree.chart.labels.StandardPieSectionLabelGenerator; |
|
|
|
import org.jfree.chart.plot.CategoryPlot; |
|
|
|
import org.jfree.chart.plot.CategoryPlot; |
|
|
|
@ -12,15 +13,20 @@ import org.jfree.chart.renderer.category.BarRenderer; |
|
|
|
import org.jfree.chart.title.LegendTitle; |
|
|
|
import org.jfree.chart.title.LegendTitle; |
|
|
|
import org.jfree.chart.ui.HorizontalAlignment; |
|
|
|
import org.jfree.chart.ui.HorizontalAlignment; |
|
|
|
import org.jfree.chart.ui.RectangleEdge; |
|
|
|
import org.jfree.chart.ui.RectangleEdge; |
|
|
|
|
|
|
|
import org.jfree.chart.ui.RectangleInsets; |
|
|
|
import org.jfree.data.category.DefaultCategoryDataset; |
|
|
|
import org.jfree.data.category.DefaultCategoryDataset; |
|
|
|
import org.jfree.data.general.DefaultPieDataset; |
|
|
|
import org.jfree.data.general.DefaultPieDataset; |
|
|
|
|
|
|
|
import org.slf4j.Logger; |
|
|
|
|
|
|
|
import org.slf4j.LoggerFactory; |
|
|
|
|
|
|
|
|
|
|
|
import java.awt.BasicStroke; |
|
|
|
import java.awt.BasicStroke; |
|
|
|
import java.awt.Color; |
|
|
|
import java.awt.Color; |
|
|
|
import java.awt.Font; |
|
|
|
import java.awt.Font; |
|
|
|
import java.awt.GraphicsEnvironment; |
|
|
|
import java.awt.GraphicsEnvironment; |
|
|
|
import java.io.ByteArrayOutputStream; |
|
|
|
import java.io.ByteArrayOutputStream; |
|
|
|
|
|
|
|
import java.io.InputStream; |
|
|
|
import java.text.DecimalFormat; |
|
|
|
import java.text.DecimalFormat; |
|
|
|
|
|
|
|
import java.util.Arrays; |
|
|
|
import java.util.LinkedHashMap; |
|
|
|
import java.util.LinkedHashMap; |
|
|
|
import java.util.LinkedHashSet; |
|
|
|
import java.util.LinkedHashSet; |
|
|
|
import java.util.List; |
|
|
|
import java.util.List; |
|
|
|
@ -37,9 +43,16 @@ import java.util.Set; |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public class ChartRenderUtil { |
|
|
|
public class ChartRenderUtil { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(ChartRenderUtil.class); |
|
|
|
|
|
|
|
|
|
|
|
private ChartRenderUtil() { |
|
|
|
private ChartRenderUtil() { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 字体探测样本文本 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private static final String FONT_PROBE_TEXT = "查实情况分布单位问题数量暂无数据"; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* 饼图配色 |
|
|
|
* 饼图配色 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@ -73,6 +86,21 @@ public class ChartRenderUtil { |
|
|
|
private static final String DEFAULT_OTHER_NAME = "其余问题类型"; |
|
|
|
private static final String DEFAULT_OTHER_NAME = "其余问题类型"; |
|
|
|
private static final String DEFAULT_EMPTY_NAME = "暂无数据"; |
|
|
|
private static final String DEFAULT_EMPTY_NAME = "暂无数据"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 候选字体,按优先级排序 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private static final String[] FONT_CANDIDATES = new String[]{ |
|
|
|
|
|
|
|
"Microsoft YaHei", "微软雅黑", |
|
|
|
|
|
|
|
"PingFang SC", "苹方-简", |
|
|
|
|
|
|
|
"Noto Sans CJK SC", "Noto Sans SC", |
|
|
|
|
|
|
|
"Source Han Sans CN", "Source Han Sans SC", |
|
|
|
|
|
|
|
"WenQuanYi Micro Hei", "文泉驿微米黑", |
|
|
|
|
|
|
|
"SimHei", "黑体", |
|
|
|
|
|
|
|
"SimSun", "宋体", |
|
|
|
|
|
|
|
"Arial Unicode MS", |
|
|
|
|
|
|
|
"SansSerif" |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// =========================
|
|
|
|
// =========================
|
|
|
|
// 对外 API:饼图(唯一入口)
|
|
|
|
// 对外 API:饼图(唯一入口)
|
|
|
|
// =========================
|
|
|
|
// =========================
|
|
|
|
@ -94,14 +122,14 @@ public class ChartRenderUtil { |
|
|
|
chartData.forEach(dataset::setValue); |
|
|
|
chartData.forEach(dataset::setValue); |
|
|
|
|
|
|
|
|
|
|
|
JFreeChart chart = ChartFactory.createPieChart( |
|
|
|
JFreeChart chart = ChartFactory.createPieChart( |
|
|
|
title, |
|
|
|
safeTitle(title), |
|
|
|
dataset, |
|
|
|
dataset, |
|
|
|
true, |
|
|
|
true, |
|
|
|
false, |
|
|
|
false, |
|
|
|
false |
|
|
|
false |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
beautifyPie(chart, chartData); |
|
|
|
beautifyPie(chart, chartData, title); |
|
|
|
return toPng(chart, w, h, "生成饼图失败"); |
|
|
|
return toPng(chart, w, h, "生成饼图失败"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -123,12 +151,12 @@ public class ChartRenderUtil { |
|
|
|
DefaultCategoryDataset dataset = new DefaultCategoryDataset(); |
|
|
|
DefaultCategoryDataset dataset = new DefaultCategoryDataset(); |
|
|
|
|
|
|
|
|
|
|
|
Map<String, Number> safeData = normalizeBarData(data); |
|
|
|
Map<String, Number> safeData = normalizeBarData(data); |
|
|
|
safeData.forEach((category, value) -> dataset.addValue(value, yLabel, category)); |
|
|
|
safeData.forEach((category, value) -> dataset.addValue(value, safeLabel(yLabel, "数量"), category)); |
|
|
|
|
|
|
|
|
|
|
|
JFreeChart chart = ChartFactory.createBarChart( |
|
|
|
JFreeChart chart = ChartFactory.createBarChart( |
|
|
|
title, |
|
|
|
safeTitle(title), |
|
|
|
xLabel, |
|
|
|
safeLabel(xLabel, "类别"), |
|
|
|
yLabel, |
|
|
|
safeLabel(yLabel, "数量"), |
|
|
|
dataset, |
|
|
|
dataset, |
|
|
|
PlotOrientation.VERTICAL, |
|
|
|
PlotOrientation.VERTICAL, |
|
|
|
true, |
|
|
|
true, |
|
|
|
@ -136,7 +164,7 @@ public class ChartRenderUtil { |
|
|
|
false |
|
|
|
false |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
beautifyBar(chart, 1); |
|
|
|
beautifyBar(chart, 1, title); |
|
|
|
return toPng(chart, w, h, "生成柱状图失败"); |
|
|
|
return toPng(chart, w, h, "生成柱状图失败"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -176,9 +204,9 @@ public class ChartRenderUtil { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
JFreeChart chart = ChartFactory.createBarChart( |
|
|
|
JFreeChart chart = ChartFactory.createBarChart( |
|
|
|
title, |
|
|
|
safeTitle(title), |
|
|
|
xLabel, |
|
|
|
safeLabel(xLabel, "类别"), |
|
|
|
yLabel, |
|
|
|
safeLabel(yLabel, "数量"), |
|
|
|
dataset, |
|
|
|
dataset, |
|
|
|
PlotOrientation.VERTICAL, |
|
|
|
PlotOrientation.VERTICAL, |
|
|
|
true, |
|
|
|
true, |
|
|
|
@ -186,7 +214,7 @@ public class ChartRenderUtil { |
|
|
|
false |
|
|
|
false |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
beautifyBar(chart, safeSeriesData.size()); |
|
|
|
beautifyBar(chart, safeSeriesData.size(), title); |
|
|
|
return toPng(chart, w, h, "生成分组柱状图失败"); |
|
|
|
return toPng(chart, w, h, "生成分组柱状图失败"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -225,29 +253,31 @@ public class ChartRenderUtil { |
|
|
|
// 美化:饼图
|
|
|
|
// 美化:饼图
|
|
|
|
// =========================
|
|
|
|
// =========================
|
|
|
|
|
|
|
|
|
|
|
|
private static void beautifyPie(JFreeChart chart, Map<String, Number> data) { |
|
|
|
private static void beautifyPie(JFreeChart chart, Map<String, Number> data, String originalTitle) { |
|
|
|
chart.setAntiAlias(true); |
|
|
|
chart.setAntiAlias(true); |
|
|
|
chart.setTextAntiAlias(true); |
|
|
|
chart.setTextAntiAlias(true); |
|
|
|
chart.setBackgroundPaint(Color.WHITE); |
|
|
|
chart.setBackgroundPaint(Color.WHITE); |
|
|
|
chart.setBorderVisible(false); |
|
|
|
chart.setBorderVisible(false); |
|
|
|
|
|
|
|
chart.setPadding(new RectangleInsets(8, 8, 8, 8)); |
|
|
|
|
|
|
|
|
|
|
|
Font titleFont = pickFont(Font.BOLD, 18); |
|
|
|
Font titleFont = pickFont(Font.BOLD, 18, safeTitle(originalTitle)); |
|
|
|
Font legendFont = pickFont(Font.PLAIN, 12); |
|
|
|
Font legendFont = pickFont(Font.PLAIN, 12, "图例"); |
|
|
|
Font labelFont = pickFont(Font.PLAIN, 11); |
|
|
|
Font labelFont = pickFont(Font.PLAIN, 11, FONT_PROBE_TEXT); |
|
|
|
|
|
|
|
|
|
|
|
if (chart.getTitle() != null) { |
|
|
|
if (chart.getTitle() != null) { |
|
|
|
chart.getTitle().setFont(titleFont); |
|
|
|
chart.getTitle().setFont(titleFont); |
|
|
|
chart.getTitle().setPaint(new Color(51, 51, 51)); |
|
|
|
chart.getTitle().setPaint(new Color(51, 51, 51)); |
|
|
|
chart.getTitle().setHorizontalAlignment(HorizontalAlignment.CENTER); |
|
|
|
chart.getTitle().setHorizontalAlignment(HorizontalAlignment.CENTER); |
|
|
|
chart.getTitle().setMargin(0, 0, 12, 0); |
|
|
|
chart.getTitle().setMargin(8, 0, 14, 0); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
LegendTitle legend = chart.getLegend(); |
|
|
|
LegendTitle legend = chart.getLegend(); |
|
|
|
if (legend != null) { |
|
|
|
if (legend != null) { |
|
|
|
legend.setItemFont(legendFont); |
|
|
|
legend.setItemFont(legendFont); |
|
|
|
legend.setBackgroundPaint(Color.WHITE); |
|
|
|
legend.setBackgroundPaint(Color.WHITE); |
|
|
|
legend.setBorder(0, 0, 0, 0); |
|
|
|
legend.setFrame(BlockBorder.NONE); |
|
|
|
legend.setPosition(RectangleEdge.TOP); |
|
|
|
legend.setPosition(RectangleEdge.TOP); |
|
|
|
|
|
|
|
legend.setMargin(0, 0, 8, 0); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
PiePlot<?> plot = (PiePlot<?>) chart.getPlot(); |
|
|
|
PiePlot<?> plot = (PiePlot<?>) chart.getPlot(); |
|
|
|
@ -290,29 +320,32 @@ public class ChartRenderUtil { |
|
|
|
// 美化:柱状图
|
|
|
|
// 美化:柱状图
|
|
|
|
// =========================
|
|
|
|
// =========================
|
|
|
|
|
|
|
|
|
|
|
|
private static void beautifyBar(JFreeChart chart, int seriesCount) { |
|
|
|
private static void beautifyBar(JFreeChart chart, int seriesCount, String originalTitle) { |
|
|
|
chart.setAntiAlias(true); |
|
|
|
chart.setAntiAlias(true); |
|
|
|
chart.setTextAntiAlias(true); |
|
|
|
chart.setTextAntiAlias(true); |
|
|
|
chart.setBackgroundPaint(Color.WHITE); |
|
|
|
chart.setBackgroundPaint(Color.WHITE); |
|
|
|
chart.setBorderVisible(false); |
|
|
|
chart.setBorderVisible(false); |
|
|
|
|
|
|
|
chart.setPadding(new RectangleInsets(8, 8, 8, 8)); |
|
|
|
|
|
|
|
|
|
|
|
Font titleFont = pickFont(Font.BOLD, 16); |
|
|
|
Font titleFont = pickFont(Font.BOLD, 16, safeTitle(originalTitle)); |
|
|
|
Font legendFont = pickFont(Font.PLAIN, 12); |
|
|
|
Font legendFont = pickFont(Font.PLAIN, 12, "图例"); |
|
|
|
Font axisFont = pickFont(Font.PLAIN, 12); |
|
|
|
Font axisFont = pickFont(Font.PLAIN, 12, FONT_PROBE_TEXT); |
|
|
|
Font valueFont = pickFont(Font.PLAIN, 11); |
|
|
|
Font valueFont = pickFont(Font.PLAIN, 11, "100"); |
|
|
|
|
|
|
|
|
|
|
|
if (chart.getTitle() != null) { |
|
|
|
if (chart.getTitle() != null) { |
|
|
|
chart.getTitle().setFont(titleFont); |
|
|
|
chart.getTitle().setFont(titleFont); |
|
|
|
chart.getTitle().setPaint(new Color(51, 51, 51)); |
|
|
|
chart.getTitle().setPaint(new Color(51, 51, 51)); |
|
|
|
chart.getTitle().setHorizontalAlignment(HorizontalAlignment.CENTER); |
|
|
|
chart.getTitle().setHorizontalAlignment(HorizontalAlignment.CENTER); |
|
|
|
|
|
|
|
chart.getTitle().setMargin(8, 0, 12, 0); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
LegendTitle legend = chart.getLegend(); |
|
|
|
LegendTitle legend = chart.getLegend(); |
|
|
|
if (legend != null) { |
|
|
|
if (legend != null) { |
|
|
|
legend.setItemFont(legendFont); |
|
|
|
legend.setItemFont(legendFont); |
|
|
|
legend.setBackgroundPaint(Color.WHITE); |
|
|
|
legend.setBackgroundPaint(Color.WHITE); |
|
|
|
legend.setBorder(0, 0, 0, 0); |
|
|
|
legend.setFrame(BlockBorder.NONE); |
|
|
|
legend.setPosition(RectangleEdge.TOP); |
|
|
|
legend.setPosition(RectangleEdge.TOP); |
|
|
|
|
|
|
|
legend.setMargin(0, 0, 8, 0); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
CategoryPlot plot = chart.getCategoryPlot(); |
|
|
|
CategoryPlot plot = chart.getCategoryPlot(); |
|
|
|
@ -320,6 +353,7 @@ public class ChartRenderUtil { |
|
|
|
plot.setOutlineVisible(false); |
|
|
|
plot.setOutlineVisible(false); |
|
|
|
plot.setRangeGridlinePaint(new Color(230, 230, 230)); |
|
|
|
plot.setRangeGridlinePaint(new Color(230, 230, 230)); |
|
|
|
plot.setRangeGridlinesVisible(true); |
|
|
|
plot.setRangeGridlinesVisible(true); |
|
|
|
|
|
|
|
plot.setInsets(new RectangleInsets(4, 8, 4, 8)); |
|
|
|
|
|
|
|
|
|
|
|
plot.getDomainAxis().setTickLabelFont(axisFont); |
|
|
|
plot.getDomainAxis().setTickLabelFont(axisFont); |
|
|
|
plot.getDomainAxis().setLabelFont(axisFont); |
|
|
|
plot.getDomainAxis().setLabelFont(axisFont); |
|
|
|
@ -331,7 +365,6 @@ public class ChartRenderUtil { |
|
|
|
renderer.setShadowVisible(false); |
|
|
|
renderer.setShadowVisible(false); |
|
|
|
renderer.setItemMargin(0.10); |
|
|
|
renderer.setItemMargin(0.10); |
|
|
|
|
|
|
|
|
|
|
|
// 多系列时适当放宽柱宽
|
|
|
|
|
|
|
|
if (seriesCount <= 1) { |
|
|
|
if (seriesCount <= 1) { |
|
|
|
renderer.setMaximumBarWidth(0.12); |
|
|
|
renderer.setMaximumBarWidth(0.12); |
|
|
|
} else if (seriesCount == 2) { |
|
|
|
} else if (seriesCount == 2) { |
|
|
|
@ -528,35 +561,105 @@ public class ChartRenderUtil { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// =========================
|
|
|
|
// =========================
|
|
|
|
// 字体兜底(避免 Linux 没微软雅黑导致中文丑/方块)
|
|
|
|
// 字体处理
|
|
|
|
// =========================
|
|
|
|
// =========================
|
|
|
|
|
|
|
|
|
|
|
|
private static Font pickFont(int style, int size) { |
|
|
|
/** |
|
|
|
try { |
|
|
|
* 从 classpath 注册字体(可选) |
|
|
|
// 按优先级尝试(Windows 常见:微软雅黑;Linux 常见:Noto/文泉驿/DejaVu)
|
|
|
|
* 例如:/fonts/NotoSansCJKsc-Regular.otf |
|
|
|
String[] candidates = new String[]{ |
|
|
|
*/ |
|
|
|
"Microsoft YaHei", "微软雅黑", |
|
|
|
public static void registerFontFromResource(String classpathLocation) { |
|
|
|
"PingFang SC", "苹方-简", |
|
|
|
if (classpathLocation == null || classpathLocation.isBlank()) { |
|
|
|
"Noto Sans CJK SC", "Noto Sans SC", |
|
|
|
return; |
|
|
|
"WenQuanYi Micro Hei", "文泉驿微米黑", |
|
|
|
} |
|
|
|
"SimSun", "宋体", |
|
|
|
|
|
|
|
"Arial Unicode MS", |
|
|
|
try (InputStream inputStream = ChartRenderUtil.class.getResourceAsStream(classpathLocation.trim())) { |
|
|
|
"SansSerif" |
|
|
|
if (inputStream == null) { |
|
|
|
}; |
|
|
|
log.warn("字体资源未找到: {}", classpathLocation); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
Font font = Font.createFont(Font.TRUETYPE_FONT, inputStream); |
|
|
|
|
|
|
|
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); |
|
|
|
|
|
|
|
boolean registered = ge.registerFont(font); |
|
|
|
|
|
|
|
log.info("注册字体完成, location={}, fontName={}, family={}, registered={}", |
|
|
|
|
|
|
|
classpathLocation, font.getFontName(), font.getFamily(), registered); |
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
|
|
log.warn("注册字体失败, location={}", classpathLocation, e); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 更稳妥的字体选择: |
|
|
|
|
|
|
|
* 1. 优先按候选名单查找 |
|
|
|
|
|
|
|
* 2. 必须校验 canDisplayUpTo(sampleText) == -1 |
|
|
|
|
|
|
|
* 3. 最后退回 SansSerif |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private static Font pickFont(int style, int size, String sampleText) { |
|
|
|
|
|
|
|
String probe = safeProbeText(sampleText); |
|
|
|
|
|
|
|
try { |
|
|
|
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); |
|
|
|
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); |
|
|
|
String[] available = ge.getAvailableFontFamilyNames(); |
|
|
|
String[] available = ge.getAvailableFontFamilyNames(); |
|
|
|
for (String c : candidates) { |
|
|
|
Set<String> availableSet = new LinkedHashSet<>(Arrays.asList(available)); |
|
|
|
for (String a : available) { |
|
|
|
|
|
|
|
if (a.equalsIgnoreCase(c)) { |
|
|
|
for (String candidate : FONT_CANDIDATES) { |
|
|
|
return new Font(a, style, size); |
|
|
|
String matchedFont = findMatchedFont(candidate, availableSet); |
|
|
|
} |
|
|
|
if (matchedFont == null) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Font font = new Font(matchedFont, style, size); |
|
|
|
|
|
|
|
int unsupportedIndex = font.canDisplayUpTo(probe); |
|
|
|
|
|
|
|
if (unsupportedIndex == -1) { |
|
|
|
|
|
|
|
log.debug("选择字体成功, candidate={}, matchedFont={}, style={}, size={}, probe={}", |
|
|
|
|
|
|
|
candidate, matchedFont, style, size, probe); |
|
|
|
|
|
|
|
return font; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
log.warn("字体存在缺字, candidate={}, matchedFont={}, unsupportedIndex={}, probe={}", |
|
|
|
|
|
|
|
candidate, matchedFont, unsupportedIndex, probe); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (Throwable e) { |
|
|
|
} catch (Throwable e) { |
|
|
|
// 字体系统初始化失败时兜底
|
|
|
|
log.warn("字体检测失败,使用 SansSerif 兜底", e); |
|
|
|
System.err.println("Font detection failed, fallback to SansSerif: " + e.getMessage()); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Font fallback = new Font("SansSerif", style, size); |
|
|
|
|
|
|
|
int unsupportedIndex = fallback.canDisplayUpTo(probe); |
|
|
|
|
|
|
|
if (unsupportedIndex != -1) { |
|
|
|
|
|
|
|
log.warn("最终兜底字体 SansSerif 仍无法完整显示文本, unsupportedIndex={}, probe={}", |
|
|
|
|
|
|
|
unsupportedIndex, probe); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
log.debug("使用兜底字体 SansSerif 成功, style={}, size={}, probe={}", style, size, probe); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return fallback; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static String findMatchedFont(String candidate, Set<String> availableSet) { |
|
|
|
|
|
|
|
for (String fontName : availableSet) { |
|
|
|
|
|
|
|
if (fontName.equalsIgnoreCase(candidate)) { |
|
|
|
|
|
|
|
return fontName; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static String safeProbeText(String text) { |
|
|
|
|
|
|
|
if (text == null || text.isBlank()) { |
|
|
|
|
|
|
|
return FONT_PROBE_TEXT; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return text.trim(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static String safeTitle(String title) { |
|
|
|
|
|
|
|
if (title == null || title.isBlank()) { |
|
|
|
|
|
|
|
return DEFAULT_EMPTY_NAME; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return title.trim(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static String safeLabel(String label, String defaultValue) { |
|
|
|
|
|
|
|
if (label == null || label.isBlank()) { |
|
|
|
|
|
|
|
return defaultValue; |
|
|
|
} |
|
|
|
} |
|
|
|
return new Font("SansSerif", style, size); |
|
|
|
return label.trim(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |