Przeglądaj źródła

解决pdf字符乱码问题

zhangchen 2 miesięcy temu
rodzic
commit
ec528e6eb1

+ 91 - 16
alien-store/src/main/java/shop/alien/store/util/StatisticsComparisonImageUtil.java

@@ -7,6 +7,7 @@ import javax.imageio.ImageIO;
 import java.awt.*;
 import java.awt.image.BufferedImage;
 import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
 import java.math.BigDecimal;
 import java.text.DecimalFormat;
 import java.util.ArrayList;
@@ -31,8 +32,23 @@ public class StatisticsComparisonImageUtil {
     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 String FONT_NAME = "Microsoft YaHei"; // 微软雅黑,如果系统没有则使用默认字体
+    // 字体:优先 classpath 内嵌字体(Linux 无微软雅黑时避免中文方框),其次系统支持中文的字体
+    private static final String FONT_CLASSPATH = "fonts/NotoSansSC-Regular.ttf";
+    private static final String[] FONT_FALLBACK_NAMES = {
+            "Microsoft YaHei",
+            "PingFang SC",
+            "WenQuanYi Zen Hei",
+            "WenQuanYi Micro Hei",
+            "Noto Sans CJK SC",
+            "Noto Sans SC",
+            "SimSun",
+            "STSong",
+            "Source Han Sans SC",
+            "DengXian"
+    };
+    private static volatile Font baseChineseFont;   // 从 TTF 加载的基准字体
+    private static volatile String chineseFontName; // 系统字体名(当未加载 TTF 时)
+
     private static final int TITLE_FONT_SIZE = 24;
     private static final int SECTION_TITLE_FONT_SIZE = 18;
     private static final int DATA_FONT_SIZE = 14;
@@ -49,6 +65,65 @@ public class StatisticsComparisonImageUtil {
     private static final DecimalFormat PERCENT_FORMAT = new DecimalFormat("#,##0.00%");
 
     /**
+     * 获取支持中文的字体(用于绘制报表,避免 Linux 下无微软雅黑导致中文方框)
+     * 优先:classpath 下 fonts/NotoSansSC-Regular.ttf;其次系统已安装的中文字体名
+     */
+    private static Font getChineseFont(int style, int size) {
+        if (baseChineseFont != null) {
+            return baseChineseFont.deriveFont(style, size);
+        }
+        if (chineseFontName != null) {
+            return new Font(chineseFontName, style, size);
+        }
+        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);
+                    return baseChineseFont.deriveFont(style, size);
+                }
+            } catch (Exception e) {
+                log.warn("加载内嵌中文字体失败,将尝试系统字体: {}", e.getMessage());
+            }
+            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+            String[] names = ge.getAvailableFontFamilyNames();
+            if (names != null) {
+                for (String candidate : FONT_FALLBACK_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);
+                            }
+                        }
+                    }
+                }
+                for (String name : names) {
+                    Font f = new Font(name, Font.PLAIN, 14);
+                    if (f.canDisplayUpTo("经") < 0) {
+                        chineseFontName = name;
+                        log.info("使用系统中文字体(自动): {}", name);
+                        return new Font(chineseFontName, style, size);
+                    }
+                }
+            }
+            chineseFontName = Font.SANS_SERIF;
+            log.warn("未找到支持中文的字体,使用 SansSerif,Linux 下中文可能显示为方框,建议在 resources/fonts/ 放入 NotoSansSC-Regular.ttf");
+            return new Font(chineseFontName, style, size);
+        }
+    }
+
+    /**
      * 将统计数据对比转换为图片字节数组
      *
      * @param comparison 统计数据对比对象
@@ -174,7 +249,7 @@ public class StatisticsComparisonImageUtil {
      * 绘制标题
      */
     private static int drawTitle(Graphics2D g2d, int y, StoreOperationalStatisticsComparisonVo comparison) {
-        Font titleFont = new Font(FONT_NAME, Font.BOLD, TITLE_FONT_SIZE);
+        Font titleFont = getChineseFont(Font.BOLD, TITLE_FONT_SIZE);
         g2d.setFont(titleFont);
         g2d.setColor(SECTION_TITLE_COLOR);
         
@@ -191,7 +266,7 @@ public class StatisticsComparisonImageUtil {
      * 绘制日期范围
      */
     private static int drawDateRange(Graphics2D g2d, int y, StoreOperationalStatisticsComparisonVo comparison) {
-        Font dateFont = new Font(FONT_NAME, Font.PLAIN, DATA_FONT_SIZE);
+        Font dateFont = getChineseFont(Font.PLAIN, DATA_FONT_SIZE);
         g2d.setFont(dateFont);
         g2d.setColor(TEXT_COLOR);
         
@@ -225,18 +300,18 @@ public class StatisticsComparisonImageUtil {
         }
         
         // 绘制区块标题
-        Font sectionFont = new Font(FONT_NAME, Font.BOLD, SECTION_TITLE_FONT_SIZE);
+        Font sectionFont = getChineseFont(Font.BOLD, SECTION_TITLE_FONT_SIZE);
         g2d.setFont(sectionFont);
         g2d.setColor(SECTION_TITLE_COLOR);
         g2d.drawString(sectionTitle, PADDING, y);
         y += 30;
-        
+
         // 绘制表头
         y = drawTableHeader(g2d, y);
-        
+
         // 绘制数据行
-        Font dataFont = new Font(FONT_NAME, Font.PLAIN, DATA_FONT_SIZE);
-        Font labelFont = new Font(FONT_NAME, Font.PLAIN, LABEL_FONT_SIZE);
+        Font dataFont = getChineseFont(Font.PLAIN, DATA_FONT_SIZE);
+        Font labelFont = getChineseFont(Font.PLAIN, LABEL_FONT_SIZE);
         
         for (DataRow row : rows) {
             y = drawDataRow(g2d, y, row, dataFont, labelFont);
@@ -254,7 +329,7 @@ public class StatisticsComparisonImageUtil {
         g2d.fillRect(PADDING, y, IMAGE_WIDTH - PADDING * 2, ROW_HEIGHT);
         
         // 绘制表头文字
-        Font headerFont = new Font(FONT_NAME, Font.BOLD, LABEL_FONT_SIZE);
+        Font headerFont = getChineseFont(Font.BOLD, LABEL_FONT_SIZE);
         g2d.setFont(headerFont);
         g2d.setColor(TEXT_COLOR);
         
@@ -650,19 +725,19 @@ public class StatisticsComparisonImageUtil {
         }
         
         // 绘制区块标题
-        Font sectionFont = new Font(FONT_NAME, Font.BOLD, SECTION_TITLE_FONT_SIZE);
+        Font sectionFont = getChineseFont(Font.BOLD, SECTION_TITLE_FONT_SIZE);
         g2d.setFont(sectionFont);
         g2d.setColor(SECTION_TITLE_COLOR);
         g2d.drawString(sectionTitle, PADDING, y);
         y += 30;
-        
-        Font dataFont = new Font(FONT_NAME, Font.PLAIN, DATA_FONT_SIZE);
-        Font labelFont = new Font(FONT_NAME, Font.PLAIN, LABEL_FONT_SIZE);
-        
+
+        Font dataFont = getChineseFont(Font.PLAIN, DATA_FONT_SIZE);
+        Font labelFont = getChineseFont(Font.PLAIN, LABEL_FONT_SIZE);
+
         // 遍历每个价目表
         for (StoreOperationalStatisticsComparisonVo.PriceListRankingComparison ranking : rankings) {
             // 绘制价目表名称(作为子标题)
-            g2d.setFont(new Font(FONT_NAME, Font.BOLD, LABEL_FONT_SIZE));
+            g2d.setFont(getChineseFont(Font.BOLD, LABEL_FONT_SIZE));
             g2d.setColor(new Color(66, 66, 66));
             String priceListName = ranking.getPriceListItemName() != null ? ranking.getPriceListItemName() : 
                 ("价目表ID: " + ranking.getPriceId());