Files
Shrlalgo.RvKits/ShrlAlgoStudio/Iconfont2CSharpView.xaml.cs
2026-02-17 22:17:23 +08:00

280 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Microsoft.Win32;
namespace ShrlAlgoStudio
{
/// <summary>
/// Iconfont2CSharpView.xaml 的交互逻辑
/// </summary>
public partial class Iconfont2CSharpView
{
public ObservableCollection<IconItem> Icons { get; set; } = new ObservableCollection<IconItem>();
// 字体预览属性
public FontFamily PreviewFontFamily
{
get { return (FontFamily)GetValue(PreviewFontFamilyProperty); }
set { SetValue(PreviewFontFamilyProperty, value); }
}
public static readonly DependencyProperty PreviewFontFamilyProperty =
DependencyProperty.Register("PreviewFontFamily", typeof(FontFamily), typeof(MainWindow), new PropertyMetadata(null));
// 内部状态
private Dictionary<int, string> _cssNameMap = new Dictionary<int, string>(); // Unicode -> CSS Name
private string _loadedFontName = "MyIcons";
private GlyphTypeface _currentGlyphTypeface;
public Iconfont2CSharpView()
{
InitializeComponent();
GridIcons.DataContext = this;
GridIcons.ItemsSource = Icons;
}
// ---------------------------------------------------------
// 1. 加载字体逻辑
// ---------------------------------------------------------
private void OnLoadFontClick(object sender, RoutedEventArgs e)
{
var dlg = new OpenFileDialog { Filter = "Font Files|*.ttf;*.otf" };
if (dlg.ShowDialog() == true)
{
LoadFont(dlg.FileName);
}
}
private void LoadFont(string path)
{
try
{
var uri = new Uri(path);
_currentGlyphTypeface = new GlyphTypeface(uri);
// 获取字体名称 (优先取英文)
_loadedFontName = _currentGlyphTypeface.FamilyNames.ContainsKey(CultureInfo.GetCultureInfo("en-US"))
? _currentGlyphTypeface.FamilyNames[CultureInfo.GetCultureInfo("en-US")]
: _currentGlyphTypeface.FamilyNames.Values.FirstOrDefault();
// 设置预览字体 (WPF Hack: file:///path/#FontName)
this.PreviewFontFamily = new FontFamily(uri, "./#" + _loadedFontName);
TxtStatus.Text = $"已加载字体: {_loadedFontName} (含 {_currentGlyphTypeface.CharacterToGlyphMap.Count} 个字符)";
RefreshData();
}
catch (Exception ex)
{
MessageBox.Show($"无法加载字体: {ex.Message}");
}
}
// ---------------------------------------------------------
// 2. 加载 CSS 映射逻辑 (核心增强)
// ---------------------------------------------------------
private void OnLoadCssClick(object sender, RoutedEventArgs e)
{
if (_currentGlyphTypeface == null)
{
MessageBox.Show("请先加载字体文件!");
return;
}
var dlg = new OpenFileDialog { Filter = "CSS Files|*.css", Title = "选择对应的 CSS 文件 (如 fontawesome.css)" };
if (dlg.ShowDialog() == true)
{
ParseCss(dlg.FileName);
RefreshData(); // 重新生成列表和代码
}
}
private void ParseCss(string path)
{
_cssNameMap.Clear();
string cssContent = File.ReadAllText(path);
// 正则表达式匹配: .class-name:before { content: "\f001"; }
// 兼容格式:单双引号,有无 content 文本
// 捕获组 1: class name (e.g. "fa-home")
// 捕获组 2: hex code (e.g. "f015")
var regex = new Regex(@"\.([a-zA-Z0-9_-]+)::?before\s*\{\s*content:\s*[""']\\([a-fA-F0-9]+)[""'];?\s*\}");
var matches = regex.Matches(cssContent);
foreach (Match match in matches)
{
string className = match.Groups[1].Value;
string hex = match.Groups[2].Value;
if (int.TryParse(hex, NumberStyles.HexNumber, null, out int unicode))
{
// 如果存在多个名字对应同一个 Unicode (alias),这里只取第一个,或者取最短的
if (!_cssNameMap.ContainsKey(unicode))
{
_cssNameMap[unicode] = className;
}
}
}
TxtStatus.Text += $" | 已解析 CSS 映射: {_cssNameMap.Count} 个名称";
}
// ---------------------------------------------------------
// 3. 数据处理与代码生成
// ---------------------------------------------------------
private void RefreshData()
{
if (_currentGlyphTypeface == null) return;
Icons.Clear();
var sortedList = new List<IconItem>();
foreach (var kvp in _currentGlyphTypeface.CharacterToGlyphMap)
{
int unicode = kvp.Key;
// 生成基础变量名
string varName;
if (_cssNameMap.ContainsKey(unicode))
{
// 将 css-kebab-case 转换为 PascalCase
// 比如 fa-arrow-circle-down -> ArrowCircleDown
varName = ConvertCssToPascal(_cssNameMap[unicode]);
}
else
{
// 只有 Unicode 时的默认回退
varName = $"Icon_{unicode:X4}";
}
sortedList.Add(new IconItem
{
Unicode = unicode,
HexDisplay = $"\\u{unicode:X4}",
Character = char.ConvertFromUtf32(unicode),
VarName = varName
});
}
// 按 Unicode 排序并添加到界面
foreach (var item in sortedList.OrderBy(i => i.Unicode))
{
Icons.Add(item);
}
GenerateCSharpCode(sortedList);
}
private void GenerateCSharpCode(List<IconItem> items)
{
bool isEnum = ChkGenerateEnum.IsChecked == true;
string cleanName = Regex.Replace(_loadedFontName, @"[^a-zA-Z0-9]", ""); // 清理类名
if (char.IsDigit(cleanName[0])) cleanName = "_" + cleanName;
StringBuilder sb = new StringBuilder();
sb.AppendLine("// -----------------------------------------------------------------------------");
sb.AppendLine($"// <auto-generated>");
sb.AppendLine($"// Font Name: {_loadedFontName}");
sb.AppendLine($"// Generated: {DateTime.Now}");
sb.AppendLine($"// </auto-generated>");
sb.AppendLine("// -----------------------------------------------------------------------------");
sb.AppendLine("using System;");
sb.AppendLine();
if (isEnum)
{
sb.AppendLine($"public enum {cleanName}Enum");
sb.AppendLine("{");
// 检查重复名称并处理
var usedNames = new HashSet<string>();
foreach (var item in items)
{
string finalName = item.VarName;
// 如果名字冲突,追加 Hex 后缀
if (usedNames.Contains(finalName)) finalName += $"_{item.Unicode:X4}";
usedNames.Add(finalName);
// 不能以数字开头
if (char.IsDigit(finalName[0])) finalName = "_" + finalName;
// 只能是 int
sb.AppendLine($" /// <summary>Hex: {item.Unicode:X4}</summary>");
sb.AppendLine($" {finalName} = 0x{item.Unicode:X4},");
}
sb.AppendLine("}");
}
else
{
sb.AppendLine($"public static class {cleanName}Icons");
sb.AppendLine("{");
var usedNames = new HashSet<string>();
foreach (var item in items)
{
string finalName = item.VarName;
if (usedNames.Contains(finalName)) finalName += $"_{item.Unicode:X4}";
usedNames.Add(finalName);
sb.AppendLine($" public const string {finalName} = \"{item.HexDisplay}\";");
}
sb.AppendLine("}");
}
TxtOutput.Text = sb.ToString();
}
// 辅助:将 css-name 转换为 PascalName
private string ConvertCssToPascal(string cssName)
{
// 移除常见的 icon 前缀 (可选)
if (cssName.StartsWith("fa-")) cssName = cssName.Substring(3);
if (cssName.StartsWith("mdi-")) cssName = cssName.Substring(4);
if (cssName.StartsWith("bi-")) cssName = cssName.Substring(3);
// 分割并首字母大写
var parts = cssName.Split(new[] { '-', '_' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < parts.Length; i++)
{
if (parts[i].Length > 0)
{
char[] a = parts[i].ToCharArray();
a[0] = char.ToUpper(a[0]);
parts[i] = new string(a);
}
}
string result = string.Join("", parts);
// 确保不是数字开头
if (char.IsDigit(result[0])) result = "_" + result;
return result;
}
private void OnCopyCodeClick(object sender, RoutedEventArgs e)
{
Clipboard.SetText(TxtOutput.Text);
MessageBox.Show("代码已复制到剪贴板");
}
}
public class IconItem
{
public int Unicode { get; set; }
public string HexDisplay { get; set; } // "\uF001"
public string Character { get; set; } // 实际字符
public string VarName { get; set; } // C# 变量名
}
}