diff --git a/.gitignore b/.gitignore
index c6d98d1..5ff6309 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,98 +1,38 @@
-# ---> JetBrains
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-
-# User-specific stuff
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/**/usage.statistics.xml
-.idea/**/dictionaries
-.idea/**/shelf
-
-# AWS User-specific
-.idea/**/aws.xml
-
-# Generated files
-.idea/**/contentModel.xml
-
-# Sensitive or high-churn files
-.idea/**/dataSources/
-.idea/**/dataSources.ids
-.idea/**/dataSources.local.xml
-.idea/**/sqlDataSources.xml
-.idea/**/dynamic.xml
-.idea/**/uiDesigner.xml
-.idea/**/dbnavigator.xml
-
-# Gradle
-.idea/**/gradle.xml
-.idea/**/libraries
-
-# Gradle and Maven with auto-import
-# When using Gradle or Maven with auto-import, you should exclude module files,
-# since they will be recreated, and may cause churn. Uncomment if using
-# auto-import.
-# .idea/artifacts
-# .idea/compiler.xml
-# .idea/jarRepositories.xml
-# .idea/modules.xml
-# .idea/*.iml
-# .idea/modules
-# *.iml
-# *.ipr
-
-# CMake
-cmake-build-*/
-
-# Mongo Explorer plugin
-.idea/**/mongoSettings.xml
-
-# File-based project format
-*.iws
-
-# IntelliJ
-out/
-
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Cursive Clojure plugin
-.idea/replstate.xml
-
-# SonarLint plugin
-.idea/sonarlint/
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
-
-# Editor-based Rest Client
-.idea/httpRequests
-
-# Android studio 3.1+ serialized cache file
-.idea/caches/build_file_checksums.ser
-
-# ---> Maven
target/
-pom.xml.tag
-pom.xml.releaseBackup
-pom.xml.versionsBackup
-pom.xml.next
-release.properties
-dependency-reduced-pom.xml
-buildNumber.properties
-.mvn/timing.properties
-# https://github.com/takari/maven-wrapper#usage-without-binary-jar
-.mvn/wrapper/maven-wrapper.jar
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
-# Eclipse m2e generated files
-# Eclipse Core
-.project
-# JDT-specific (Eclipse Java Development Tools)
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..aa00ffa
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..fdc35ea
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..2b73913
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,43 @@
+
+
+ 4.0.0
+
+ com.imyeyu.fx.icon
+ timi-fx-icon
+ 0.0.1
+
+
+ 21.0.2
+ 21
+ 21
+ UTF-8
+
+
+
+
+ org.openjfx
+ javafx-controls
+ ${fx.version}
+
+
+ com.imyeyu.io
+ timi-io
+ 0.0.1
+ test
+
+
+ org.dom4j
+ dom4j
+ 2.1.4
+ test
+
+
+ com.google.code.gson
+ gson
+ 2.10
+ test
+
+
+
diff --git a/src/main/java/com/imyeyu/fx/icon/TimiFXIcon.java b/src/main/java/com/imyeyu/fx/icon/TimiFXIcon.java
new file mode 100644
index 0000000..2477365
--- /dev/null
+++ b/src/main/java/com/imyeyu/fx/icon/TimiFXIcon.java
@@ -0,0 +1,384 @@
+package com.imyeyu.fx.icon;
+
+import javafx.scene.SnapshotParameters;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.image.Image;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.text.Font;
+import javafx.scene.text.Text;
+import com.imyeyu.fx.icon.util.ImageConvertor;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * JavaFX 像素风字体图标库,这些图标基于 16x 像素绘制,即图标在 16 32 64.. 等字号时渲染最清晰准确
+ *
+ *
图标名称可在 https://icon.imyeyu.net/ 查询
+ *
+ *
+ * // 静态类,不需要实例化,直接调用静态方法
+ * TimiFXIcon.fromName("FILE");
+ *
+ *
+ * @author 夜雨
+ * @since 2022-07-28 09:57
+ */
+public class TimiFXIcon {
+
+ // ---------- 公开属性 ----------
+
+ /** 默认颜色 */
+ public static final Paint COLOR = Paint.valueOf("#333");
+
+ /** 字体文件资源路径 */
+ public static final String FONT_PATH = "font/timi-icon.ttf";
+
+ // ---------- 私有属性 ----------
+
+ /** 图标名称映射 Unicode 序列化文件路径 */
+ private static final String NAME_MAPPING_PATH = "font/timi-icon.mapping";
+
+ /** 组件样式类 */
+ private static final String STYLE_CLASS = "timi-icon";
+
+ /** 字体文件字节 */
+ private static byte[] bytes;
+
+ /** 用于转换渲染的画板 */
+ private static final Canvas CANVAS = new Canvas();
+ private static final GraphicsContext G2D = CANVAS.getGraphicsContext2D();
+
+ /** 图标字体缓存 Map<字号, 字体> */
+ private static final Map FONT_MAPPING = new HashMap<>();
+
+ /** 映射缓存 Map<图标名, Unicode> */
+ private static Map nameMapping;
+
+ /** 这个类不可实例化 */
+ private TimiFXIcon() {
+ }
+
+ /** 读取图标与 Unicode 的映射,由项目测试环境生成 */
+ @SuppressWarnings("unchecked")
+ private static synchronized void loadMapping() {
+ if (nameMapping == null) {
+ try {
+ InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(NAME_MAPPING_PATH);
+ if (is == null) {
+ throw new NullPointerException("not found /font/timi-icon.ttf file");
+ }
+ ObjectInputStream ois = new ObjectInputStream(is);
+ nameMapping = (Map) ois.readObject();
+ ois.close();
+ is.close();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /**
+ * 获取名称字符映射表
+ *
+ * @return 名称字符映射表
+ */
+ public static Map getNameMapping() {
+ loadMapping();
+ return new HashMap<>(nameMapping); // 阻止修改
+ }
+
+ /**
+ * 获取字体文件数据流
+ *
+ * @return 字体文件数据流
+ */
+ public static InputStream getInputStream() {
+ if (bytes == null) {
+ try {
+ InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(FONT_PATH);
+ if (is == null) {
+ throw new NullPointerException("not found /font/timi-icon.ttf file");
+ }
+ bytes = is.readAllBytes();
+ is.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return new ByteArrayInputStream(bytes);
+ }
+
+ /**
+ * 获取字体
+ *
+ * @param size 字号
+ * @return 字体
+ */
+ public static synchronized Font getFont(int size) {
+ if (FONT_MAPPING.containsKey(size)) {
+ return FONT_MAPPING.get(size);
+ }
+ Font font = Font.loadFont(getInputStream(), size);
+ FONT_MAPPING.put(size, font);
+ return font;
+ }
+
+ // ---------- 根据名称获取图标字符 ----------
+
+ /**
+ * 根据名称获取图标字符
+ *
+ * @param name 名称
+ * @return 字符
+ */
+ public static char toChar(String name) {
+ if (nameMapping == null) {
+ loadMapping();
+ }
+ Character c = nameMapping.get(name.toLowerCase());
+ if (c == null) {
+ throw new NullPointerException("not found char from the name: " + name);
+ }
+ return c;
+ }
+
+ // ---------- 根据名称获取图标文本 ----------
+
+ /**
+ * 根据名称获取图标
+ *
+ * @param name 名称
+ * @return 图标文本
+ */
+ public static Text fromName(String name) {
+ return fromName(name, 16, COLOR);
+ }
+
+ /**
+ * 根据名称获取图标
+ *
+ * @param name 名称
+ * @param size 字号
+ * @return 图标文本
+ */
+ public static Text fromName(String name, int size) {
+ return fromName(name, size, COLOR);
+ }
+
+ /**
+ * 根据名称获取图标
+ *
+ * @param name 名称
+ * @param fill 填充色
+ * @return 图标文本
+ */
+ public static Text fromName(String name, Paint fill) {
+ return fromName(name, 16, fill);
+ }
+
+ /**
+ * 根据名称获取图标
+ *
+ * @param name 名称
+ * @param size 字号
+ * @param fill 填充色
+ * @return 图标文本
+ */
+ public static Text fromName(String name, int size, Paint fill) {
+ return fromChar(toChar(name), size, fill);
+ }
+
+ // ---------- 根据字符获取图标文本 ----------
+
+ /**
+ * 根据字符获取图标
+ *
+ * @param c 字符
+ * @return 图标文本
+ */
+ public static Text fromChar(char c) {
+ return fromChar(c, 16, COLOR);
+ }
+
+ /**
+ * 根据字符获取图标
+ *
+ * @param c 字符
+ * @param size 字号
+ * @return 图标文本
+ */
+ public static Text fromChar(char c, int size) {
+ return fromChar(c, size, COLOR);
+ }
+
+ /**
+ * 根据字符获取图标
+ *
+ * @param c 字符
+ * @param fill 填充色
+ * @return 图标文本
+ */
+ public static Text fromChar(char c, Paint fill) {
+ return fromChar(c, 16, fill);
+ }
+
+ /**
+ * 根据字符获取图标
+ *
+ * @param c 字符
+ * @param size 字号
+ * @param fill 填充色
+ * @return 图标文本
+ */
+ public static Text fromChar(char c, int size, Paint fill) {
+ Text text = new Text(String.valueOf(c));
+ text.getStyleClass().setAll(STYLE_CLASS);
+ text.setFont(getFont(size));
+ text.setFill(fill);
+ return text;
+ }
+
+ // ---------- 根据名称获取图像 ----------
+
+ /**
+ * 根据名称获取图像
+ *
+ * @param name 名称
+ * @return 图像
+ */
+ public static Image imageFromName(String name) {
+ return imageFromName(name, 16, COLOR);
+ }
+
+ /**
+ * 根据名称获取图像
+ *
+ * @param name 名称
+ * @param fill 填充
+ * @return 图像
+ */
+ public static Image imageFromName(String name, Paint fill) {
+ return imageFromName(name, 16, fill);
+ }
+
+ /**
+ * 根据名称获取图像
+ *
+ * @param name 名称
+ * @param size 尺寸
+ * @return 图像
+ */
+ public static Image imageFromName(String name, int size) {
+ return imageFromName(name, size, COLOR);
+ }
+
+ /**
+ * 根据名称获取图像
+ *
+ * @param name 名称
+ * @param size 尺寸
+ * @param fill 填充
+ * @return 图像
+ */
+ public static Image imageFromName(String name, int size, Paint fill) {
+ return imageFromChar(toChar(name), size, fill);
+ }
+
+ // ---------- 根据字符获取图像 ----------
+
+ /**
+ * 图标字符转图像
+ *
+ * @param c 字符
+ * @return 图像
+ */
+ public static Image imageFromChar(char c) {
+ return imageFromChar(c, 16, COLOR);
+ }
+
+ /**
+ * 图标字符转图像
+ *
+ * @param c 字符
+ * @param size 尺寸
+ * @return 图像
+ */
+ public static Image imageFromChar(char c, int size) {
+ return imageFromChar(c, size, COLOR);
+ }
+
+ /**
+ * 图标字符转图像
+ *
+ * @param c 字符
+ * @param fill 填充
+ * @return 图像
+ */
+ public static Image imageFromChar(char c, Paint fill) {
+ return imageFromChar(c, 16, fill);
+ }
+
+ /**
+ * 图标字符转图像
+ *
+ * @param c 字符
+ * @param size 尺寸
+ * @param fill 填充
+ * @return 图像
+ */
+ public static Image imageFromChar(char c, int size, Paint fill) {
+ CANVAS.setWidth(size);
+ CANVAS.setHeight(size);
+ G2D.clearRect(0, 0, CANVAS.getWidth(), CANVAS.getHeight());
+ G2D.setFont(getFont(size));
+ G2D.setFill(fill);
+ G2D.fillText(String.valueOf(c), 0, size - size / 16D);
+
+ SnapshotParameters sp = new SnapshotParameters();
+ sp.setFill(Color.TRANSPARENT);
+ return CANVAS.snapshot(sp, null);
+ }
+
+ // ---------- 根据名称获取重绘图像 ----------
+
+ /**
+ * 根据名称获取图标(产生重绘,可用在 Stage 图标)
+ *
+ * @param name 名称
+ * @return 图标
+ */
+ public static Image iconFromName(String name) {
+ return iconFromName(name, COLOR);
+ }
+
+ /**
+ * 根据名称获取图标(产生重绘,可用在 Stage 图标)
+ *
+ * @param name 名称
+ * @param fill 填充
+ * @return 图标
+ */
+ public static Image iconFromName(String name, Paint fill) {
+ try {
+ BufferedImage bImage = ImageConvertor.toAWTImage(imageFromName(name, fill));
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ImageIO.write(bImage, "PNG", baos);
+ bImage.flush();
+ baos.flush();
+ baos.close();
+ return new Image(new ByteArrayInputStream(baos.toByteArray()));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/com/imyeyu/fx/icon/package-info.java b/src/main/java/com/imyeyu/fx/icon/package-info.java
new file mode 100644
index 0000000..c308412
--- /dev/null
+++ b/src/main/java/com/imyeyu/fx/icon/package-info.java
@@ -0,0 +1,2 @@
+/** 主程序 */
+package com.imyeyu.fx.icon;
diff --git a/src/main/java/com/imyeyu/fx/icon/util/ImageConvertor.java b/src/main/java/com/imyeyu/fx/icon/util/ImageConvertor.java
new file mode 100644
index 0000000..2706bc2
--- /dev/null
+++ b/src/main/java/com/imyeyu/fx/icon/util/ImageConvertor.java
@@ -0,0 +1,96 @@
+package com.imyeyu.fx.icon.util;
+
+import javafx.scene.image.Image;
+import javafx.scene.image.PixelFormat;
+import javafx.scene.image.PixelReader;
+import javafx.scene.image.PixelWriter;
+import javafx.scene.image.WritableImage;
+import javafx.scene.image.WritablePixelFormat;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.awt.image.SampleModel;
+import java.awt.image.SinglePixelPackedSampleModel;
+import java.nio.IntBuffer;
+
+/**
+ * JavaFX 和 AWT 图片转换
+ *
+ * @author 夜雨
+ * @since 2022-10-09 23:16
+ */
+public class ImageConvertor {
+
+ /**
+ * AWT 图像转 JavaFX 图像
+ *
+ * @param bimg AWT 图像
+ * @return JavaFX 图像
+ */
+ public static WritableImage toFXImage(BufferedImage bimg) {
+ int bw = bimg.getWidth();
+ int bh = bimg.getHeight();
+ switch (bimg.getType()) {
+ case BufferedImage.TYPE_INT_ARGB:
+ case BufferedImage.TYPE_INT_ARGB_PRE:
+ break;
+ default:
+ BufferedImage converted = new BufferedImage(bw, bh, BufferedImage.TYPE_INT_ARGB_PRE);
+ Graphics2D g2d = converted.createGraphics();
+ g2d.drawImage(bimg, 0, 0, null);
+ g2d.dispose();
+ bimg = converted;
+ break;
+ }
+ WritableImage img = new WritableImage(bw, bh);
+ PixelWriter pw = img.getPixelWriter();
+ DataBufferInt db = (DataBufferInt) bimg.getRaster().getDataBuffer();
+ int[] data = db.getData();
+ int offset = bimg.getRaster().getDataBuffer().getOffset();
+ int scan = 0;
+ SampleModel sm = bimg.getRaster().getSampleModel();
+ if (sm instanceof SinglePixelPackedSampleModel) {
+ scan = ((SinglePixelPackedSampleModel) sm).getScanlineStride();
+ }
+
+ PixelFormat pf = (bimg.isAlphaPremultiplied() ? PixelFormat.getIntArgbPreInstance() : PixelFormat.getIntArgbInstance());
+ pw.setPixels(0, 0, bw, bh, pf, data, offset, scan);
+ return img;
+ }
+
+ /**
+ * JavaFX 图像转 AWT 图像
+ *
+ * @param img JavaFX 图像
+ * @return AWT 图像
+ */
+ public static BufferedImage toAWTImage(Image img) {
+ PixelReader pr = img.getPixelReader();
+ int iw = (int) img.getWidth();
+ int ih = (int) img.getHeight();
+ PixelFormat> fxFormat = pr.getPixelFormat();
+ int prefImgType = switch (fxFormat.getType()) {
+ case BYTE_BGRA_PRE, INT_ARGB_PRE -> BufferedImage.TYPE_INT_ARGB_PRE;
+ case BYTE_BGRA, INT_ARGB -> BufferedImage.TYPE_INT_ARGB;
+ case BYTE_RGB -> BufferedImage.TYPE_INT_RGB;
+ case BYTE_INDEXED -> (fxFormat.isPremultiplied() ? BufferedImage.TYPE_INT_ARGB_PRE : BufferedImage.TYPE_INT_ARGB);
+ };
+ BufferedImage bImg = new BufferedImage(iw, ih, prefImgType);
+ DataBufferInt db = (DataBufferInt) bImg.getRaster().getDataBuffer();
+ int[] data = db.getData();
+ int offset = bImg.getRaster().getDataBuffer().getOffset();
+ int scan = 0;
+ SampleModel sm = bImg.getRaster().getSampleModel();
+ if (sm instanceof SinglePixelPackedSampleModel) {
+ scan = ((SinglePixelPackedSampleModel) sm).getScanlineStride();
+ }
+ WritablePixelFormat pf = switch (bImg.getType()) {
+ case BufferedImage.TYPE_INT_RGB, BufferedImage.TYPE_INT_ARGB_PRE -> PixelFormat.getIntArgbPreInstance();
+ case BufferedImage.TYPE_INT_ARGB -> PixelFormat.getIntArgbInstance();
+ default -> throw new InternalError("Failed to validate BufferedImage type");
+ };
+ pr.getPixels(0, 0, iw, ih, pf, data, offset, scan);
+ return bImg;
+ }
+}
diff --git a/src/main/java/com/imyeyu/fx/icon/util/package-info.java b/src/main/java/com/imyeyu/fx/icon/util/package-info.java
new file mode 100644
index 0000000..9510956
--- /dev/null
+++ b/src/main/java/com/imyeyu/fx/icon/util/package-info.java
@@ -0,0 +1,2 @@
+/** 工具 */
+package com.imyeyu.fx.icon.util;
diff --git a/src/main/resources/font/timi-icon.mapping b/src/main/resources/font/timi-icon.mapping
new file mode 100644
index 0000000..3a9c579
Binary files /dev/null and b/src/main/resources/font/timi-icon.mapping differ
diff --git a/src/main/resources/font/timi-icon.ttf b/src/main/resources/font/timi-icon.ttf
new file mode 100644
index 0000000..4fb2ed5
Binary files /dev/null and b/src/main/resources/font/timi-icon.ttf differ
diff --git a/src/test/java/test/SerializeMapping.java b/src/test/java/test/SerializeMapping.java
new file mode 100644
index 0000000..d7d6a50
--- /dev/null
+++ b/src/test/java/test/SerializeMapping.java
@@ -0,0 +1,80 @@
+package test;
+
+import com.google.gson.GsonBuilder;
+import com.imyeyu.io.IO;
+import com.imyeyu.utils.Encoder;
+import com.imyeyu.utils.Time;
+import org.dom4j.Document;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.ObjectOutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * IcoMoon 生成的 xml 映射序列化为 Map<图标名, Unicode> 对象
+ *
+ * @author 夜雨
+ * @since 2022-07-28 21:16
+ */
+public class SerializeMapping {
+
+ public static void main(String[] args) throws Exception {
+ String subVersion = "a";
+
+ String versionExt = Time.ymd.format(Time.now()) + subVersion;
+ // 输出映射文件
+ File jsonFile = new File("src/test/resources/timi-icon_%s.json".formatted(versionExt));
+ File serializeFile = new File("src/main/resources/font/timi-icon.mapping");
+ System.out.println("serialize to " + serializeFile.getAbsolutePath());
+
+ // 读取 SVG 映射
+ String svgPath = "timi-icon.svg";
+ Document dom = new SAXReader().read(Thread.currentThread().getContextClassLoader().getResourceAsStream(svgPath));
+ List> glyphs = dom.getRootElement().element("defs").element("font").elements();
+ System.out.println("read dom xml successes");
+
+ // 解析 Unicode 映射
+ Map json = new HashMap<>();
+ Map map = new HashMap<>();
+ for (int i = 0; i < glyphs.size(); i++) {
+ if (glyphs.get(i) instanceof Element glyph) {
+ String name = glyph.attributeValue("glyph-name");
+ String unicode = glyph.attributeValue("unicode");
+
+ if (name != null) {
+ json.put(name.trim(), Encoder.unicode(unicode));
+ map.put(name.trim(), unicode.charAt(0));
+ }
+ }
+ }
+ System.out.println("parse finish: map size = " + map.size());
+
+ // 序列化文件
+ IO.toFile(jsonFile, new GsonBuilder().setPrettyPrinting().create().toJson(json).replaceAll(" ", "\t").replaceAll("\\\\\\\\", "\\\\"));
+
+ FileOutputStream fos = new FileOutputStream(serializeFile);
+ ObjectOutputStream oos = new ObjectOutputStream(fos);
+ oos.writeObject(map);
+ oos.close();
+ fos.close();
+
+ Path ttfFrom = new File("src/test/resources/timi-icon.ttf").toPath();
+ Path ttfTo = new File("src/main/resources/font/timi-icon.ttf").toPath();
+ Files.copy(ttfFrom, ttfTo, StandardCopyOption.REPLACE_EXISTING);
+
+ IO.rename(new File("src/test/resources/timi-icon.svg"), "timi-icon_%s.svg".formatted(versionExt));
+ IO.rename(new File("src/test/resources/timi-icon.eot"), "timi-icon_%s.eot".formatted(versionExt));
+ IO.rename(new File("src/test/resources/timi-icon.ttf"), "timi-icon_%s.ttf".formatted(versionExt));
+ IO.rename(new File("src/test/resources/timi-icon.woff"), "timi-icon_%s.woff".formatted(versionExt));
+
+ System.out.println("serialize mapping successes");
+ }
+}
\ No newline at end of file