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

595 lines
23 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using Microsoft.Win32;
using Newtonsoft.Json;
namespace ShrlAlgoStudio
{
public class FullMapping : INotifyPropertyChanged
{
private static Dictionary<string, FontFamily> _systemFontDict;
public static void Initialize(Dictionary<string, FontFamily> fontDict)
{
_systemFontDict = fontDict;
}
public string OriginalFont { get; set; }
public string OriginalStyle { get; set; }
private string newFont;
public string NewFont
{
get => newFont;
set
{
if (newFont != value)
{
newFont = value;
OnPropertyChanged(nameof(NewFont));
UpdateAvailableStyles();
}
}
}
private string newStyle;
public string NewStyle
{
get => newStyle;
set
{
newStyle = value;
OnPropertyChanged(nameof(NewStyle));
}
}
public ObservableCollection<string> AvailableNewStyles { get; } = [];
public bool IsNewFontInSystem { get; private set; }
[JsonIgnore] private string usageInfo;
[JsonIgnore]
public string UsageInfo
{
get => usageInfo;
set
{
usageInfo = value;
OnPropertyChanged(nameof(UsageInfo));
}
}
private void UpdateAvailableStyles()
{
IsNewFontInSystem = _systemFontDict.TryGetValue(NewFont, out var fontFamily);
Application.Current.Dispatcher.Invoke(() => AvailableNewStyles.Clear());
if (IsNewFontInSystem)
{
var styles = new List<string>();
foreach (var typeface in fontFamily.GetTypefaces())
{
styles.Add(typeface.FaceNames[System.Windows.Markup.XmlLanguage.GetLanguage("en-us")]);
}
styles.Distinct().OrderBy(s => s).ToList().ForEach(s => Application.Current.Dispatcher.Invoke(() => AvailableNewStyles.Add(s)));
if (!AvailableNewStyles.Contains(NewStyle))
{
NewStyle = AvailableNewStyles.Contains("Regular") ? "Regular" : AvailableNewStyles.FirstOrDefault();
}
}
OnPropertyChanged(nameof(IsNewFontInSystem));
OnPropertyChanged(nameof(AvailableNewStyles));
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public partial class DrfxFontFixerView
{
public ObservableCollection<FullMapping> FullMappings { get; set; } = new ObservableCollection<FullMapping>();
public List<string> SystemFonts { get; set; } = new List<string>();
private readonly string mappingFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "mapping.json");
private bool isProcessingComplete = false;
private string lastOutputDirectory = "";
public DrfxFontFixerView()
{
InitializeComponent();
this.DataContext = this;
LoadSystemFonts();
OutputPath.Text = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"Blackmagic Design",
"DaVinci Resolve",
"Support",
"Fusion",
"Templates"
);
LoadMapping(false);
MappingDataGrid.ItemsSource = FullMappings;
}
#region Initialization, Persistence and Font Management
private void LoadSystemFonts()
{
var fontDict = new Dictionary<string, FontFamily>();
foreach (var fontFamily in Fonts.SystemFontFamilies.OrderBy(f => f.Source))
{
if (!fontDict.ContainsKey(fontFamily.Source)) fontDict.Add(fontFamily.Source, fontFamily);
}
SystemFonts = fontDict.Keys.ToList();
FullMapping.Initialize(fontDict);
}
private void SaveMapping()
{
try
{
string json = JsonConvert.SerializeObject(FullMappings, Formatting.Indented);
File.WriteAllText(mappingFilePath, json);
Log("映射表已成功保存。", Brushes.Blue);
}
catch (Exception ex)
{
Log($"保存映射表失败: {ex.Message}", Brushes.Red);
}
}
private void LoadMapping(bool userInitiated = true)
{
if (!File.Exists(mappingFilePath))
{
if (userInitiated) MessageBox.Show("未找到 mapping.json 文件。", "提示");
return;
}
try
{
string json = File.ReadAllText(mappingFilePath);
var loaded = JsonConvert.DeserializeObject<ObservableCollection<FullMapping>>(json);
if (loaded != null)
{
FullMappings = loaded;
MappingDataGrid.ItemsSource = null;
MappingDataGrid.ItemsSource = FullMappings;
Log("已成功加载本地映射表。", Brushes.Blue);
}
}
catch (Exception ex)
{
Log($"加载映射表失败: {ex.Message}", Brushes.Red);
}
}
#endregion
#region UI Event Handlers
private void NewFontComboBox_LostFocus(object sender, RoutedEventArgs e)
{
var comboBox = sender as ComboBox;
var mapping = comboBox?.DataContext as FullMapping;
if (mapping != null && mapping.NewFont != comboBox.Text)
{
mapping.NewFont = comboBox.Text;
}
}
private void NewStyleComboBox_Loaded(object sender, RoutedEventArgs e)
{
var comboBox = sender as ComboBox;
var mapping = comboBox?.DataContext as FullMapping;
if (mapping != null)
{
comboBox.ItemsSource = mapping.AvailableNewStyles;
}
}
private async void StartButton_Click(object sender, RoutedEventArgs e)
{
if (FileListBox.Items.Count == 0)
{
MessageBox.Show("请先添加 .drfx 文件。");
return;
}
if (FullMappings.Count == 0)
{
MessageBox.Show("映射表为空。");
return;
}
var files = FileListBox.Items.Cast<string>().ToList();
SetUiState(false);
LogTextBlock.Text = "";
MainProgressBar.Value = 0;
isProcessingComplete = false;
try
{
var progress = new Progress<ProgressReport>(r =>
{
if (!string.IsNullOrEmpty(r.Message)) Log(r.Message, r.Color);
MainProgressBar.Value = r.Percentage;
});
var outputPath = OutputPath.Text;
await Task.Run(() => ProcessFiles_Mapping(files, outputPath, FullMappings.ToList(), progress));
Log("====== 所有任务已完成! ======", Brushes.Green);
isProcessingComplete = true;
var result = MessageBox.Show("处理完成!是否打开输出目录", "完成", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result== MessageBoxResult.Yes)
{
Process.Start("explorer.exe", outputPath);
}
}
catch (Exception ex)
{
Log($"严重错误: {ex.Message}", Brushes.Red);
}
finally
{
SetUiState(true);
}
}
private async void ScanFilesButton_Click(object sender, RoutedEventArgs e)
{
if (FileListBox.Items.Count == 0)
{
MessageBox.Show("请先添加要扫描的 .drfx 文件。");
return;
}
SetUiState(false);
Log("开始扫描文件...");
var usageDict = new Dictionary<Tuple<string, string>, List<string>>();
try
{
await Task.Run(() =>
{
foreach (var drfxFile in FileListBox.Items.Cast<string>())
{
using (var archive = ZipFile.OpenRead(drfxFile))
{
foreach (var entry in archive.Entries.Where(en => en.Name.EndsWith(".setting", StringComparison.OrdinalIgnoreCase)))
{
using (var entryStream = entry.Open())
using (var memoryStream = new MemoryStream())
{
entryStream.CopyTo(memoryStream);
byte[] contentBytes = memoryStream.ToArray();
using (var readableStream = new MemoryStream(contentBytes))
{
var encoding = GetFileEncoding(readableStream);
string content = encoding.GetString(contentBytes);
var foundPairs = ExtractFontAndStylePairs(content);
foreach (var pair in foundPairs)
{
if (!usageDict.ContainsKey(pair))
{
usageDict[pair] = new List<string>();
}
usageDict[pair].Add(entry.Name);
}
}
}
}
}
}
});
int newItems = 0;
var existingMappings = FullMappings.ToDictionary(m => (m.OriginalFont, m.OriginalStyle));
foreach (var entry in usageDict.OrderBy(p => p.Key.Item1).ThenBy(p => p.Key.Item2))
{
var pair = (entry.Key.Item1, entry.Key.Item2);
string usageInfo = $"使用文件:\n{string.Join("\n", entry.Value.Distinct())}";
if (existingMappings.TryGetValue(pair, out var existingMapping))
{
existingMapping.UsageInfo = usageInfo;
}
else
{
FullMappings.Add(new FullMapping
{
OriginalFont = pair.Item1, OriginalStyle = pair.Item2, NewFont = pair.Item1, NewStyle = pair.Item2, UsageInfo = usageInfo
});
newItems++;
}
}
Log($"扫描完成,新增 {newItems} 个组合,并已更新所有文件引用信息。", Brushes.Blue);
}
catch (Exception ex)
{
Log($"扫描失败: {ex.Message}", Brushes.Red);
}
finally
{
SetUiState(true);
}
}
private void SaveMapButton_Click(object sender, RoutedEventArgs e) => SaveMapping();
private void LoadMapButton_Click(object sender, RoutedEventArgs e)
{
if (MessageBox.Show("加载将覆盖当前更改,确定吗?", "确认", MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
{
LoadMapping();
}
}
private void ClearMapButton_Click(object sender, RoutedEventArgs e)
{
if (MessageBox.Show("确定要清空映射表吗?", "确认", MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
{
FullMappings.Clear();
Log("映射表已清空。");
}
}
#endregion
#region Core Processing Logic
private void ProcessFiles_Mapping(List<string> files, string outputDir, List<FullMapping> mappings, IProgress<ProgressReport> progress)
{
int filesCompleted = 0;
var mappingDict = mappings.ToDictionary(m => (m.OriginalFont, m.OriginalStyle));
foreach (var drfxFile in files)
{
int totalReplacementsInFile = 0;
string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
try
{
progress.Report(new ProgressReport { Message = $"---处理: {Path.GetFileName(drfxFile)} ---" });
ZipFile.ExtractToDirectory(drfxFile, tempDir);
var settingFiles = Directory.GetFiles(tempDir, "*.setting", SearchOption.AllDirectories);
foreach (var settingFile in settingFiles)
{
var (modifiedContent, replacementsCount) = ProcessSingleFile(settingFile, mappingDict, progress);
if (replacementsCount > 0)
{
Encoding encoding;
using (var stream = new FileStream(settingFile, FileMode.Open, FileAccess.Read))
{
encoding = GetFileEncoding(stream);
}
File.WriteAllText(settingFile, modifiedContent, encoding);
totalReplacementsInFile += replacementsCount;
}
}
if (!Directory.Exists(outputDir))
{
Directory.CreateDirectory(outputDir);
}
lastOutputDirectory = outputDir;
string finalDrfxPath = Path.Combine(outputDir, Path.GetFileName(drfxFile));
if (File.Exists(finalDrfxPath)) File.Delete(finalDrfxPath);
ZipFile.CreateFromDirectory(tempDir, finalDrfxPath, CompressionLevel.Optimal, false);
progress.Report(totalReplacementsInFile > 0
? new ProgressReport { Message = $" - 总结: 共应用 {totalReplacementsInFile} 条替换规则。", Color = Brushes.Purple }
: new ProgressReport { Message = $" - 总结: 无需任何替换。", Color = Brushes.Gray });
progress.Report(new ProgressReport { Message = $" -> 成功! 已保存到 '{outputDir}' 文件夹。", Color = Brushes.Blue });
}
catch (Exception ex)
{
progress.Report(new ProgressReport { Message = $" -> 失败: {ex.Message}", Color = Brushes.Red });
}
finally
{
filesCompleted++;
progress.Report(new ProgressReport { Percentage = (int)(100.0 * filesCompleted / files.Count) });
if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true);
}
}
}
private (string ModifiedContent, int ReplacementsCount) ProcessSingleFile(string filePath,
Dictionary<(string, string), FullMapping> mappingDict, IProgress<ProgressReport> progress)
{
Encoding encoding;
// if (filePath.Contains("Title 03"))
// {
// }
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
encoding = GetFileEncoding(stream);
}
string originalContent = File.ReadAllText(filePath, encoding);
int replacementsCount = 0;
string toolBlockPattern =
@"\w+\s*=\s*(?:TextPlus)\s*(\{((?>[^{}]+|\{(?<open>)|\}(?<-open>))*(?(open)(?!)))\})";
string modifiedContent = Regex.Replace(originalContent, toolBlockPattern, (match) =>
{
string toolBlockContent = match.Value;
string font = ExtractValue(toolBlockContent, "Font");
string style = ExtractValue(toolBlockContent, "Style");
if (font != null && style != null && mappingDict.TryGetValue((font, style), out var rule))
{
string newBlock = toolBlockContent;
if (rule.NewFont != font)
{
newBlock = Regex.Replace(newBlock, GetPattern("Font"), $"$1{rule.NewFont}$2");
progress.Report(new ProgressReport
{ Message = $" - 字体映射: '{font}' -> '{rule.NewFont}' (文件: {Path.GetFileName(filePath)})", Color = Brushes.DarkGreen });
replacementsCount++;
}
if (rule.NewStyle != style)
{
newBlock = Regex.Replace(newBlock, GetPattern("Style"), $"$1{rule.NewStyle}$2");
progress.Report(new ProgressReport
{ Message = $" - 样式映射: '{style}' -> '{rule.NewStyle}' (文件: {Path.GetFileName(filePath)})", Color = Brushes.DarkCyan });
replacementsCount++;
}
return newBlock;
}
return toolBlockContent;
});
return (modifiedContent, replacementsCount);
}
#endregion
#region Helper Methods
// --- 核心BUG终极修复: 使用两个捕获组,确保替换的完整性 ---
private string GetPattern(string fieldName) => $@"({GetExtractionPattern(fieldName, true)})[^""]*({GetTrailingPattern()})";
// 这个辅助方法只用于 GetPattern
private string GetTrailingPattern() => @"""(,?)\s*\S*\s*\}";
// 这个辅助方法只用于 GetPattern
private string GetExtractionPattern(string fieldName, bool forCapture)
{
string pattern = $@"\b{fieldName}\s*=\s*Input\s*\{{[^{{}}]*?Value\s*=\s*""";
return forCapture ? pattern : pattern + @"(?<value>[^""]*)""";
}
private string ExtractValue(string content, string fieldName)
{
var match = Regex.Match(content, GetExtractionPattern(fieldName, false));
return match.Success ? match.Groups["value"].Value : null;
}
private List<Tuple<string, string>> ExtractFontAndStylePairs(string content)
{
var pairs = new List<Tuple<string, string>>();
string toolBlockPattern =
@"\w+\s*=\s*(?:TextPlus)\s*(\{((?>[^{}]+|\{(?<open>)|\}(?<-open>))*(?(open)(?!)))\})";
var matches = Regex.Matches(content, toolBlockPattern);
foreach (Match match in matches)
{
string font = ExtractValue(match.Value, "Font");
string style = ExtractValue(match.Value, "Style");
if (font != null && style != null)
{
pairs.Add(new Tuple<string, string>(font, style));
}
}
return pairs;
}
private Encoding GetFileEncoding(Stream stream)
{
var bom = new byte[4];
stream.Read(bom, 0, 4);
stream.Position = 0;
if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) return Encoding.UTF8;
if (bom[0] == 0xff && bom[1] == 0xfe) return Encoding.Unicode;
if (bom[0] == 0xfe && bom[1] == 0xff) return Encoding.BigEndianUnicode;
return new UTF8Encoding(false);
}
private void SetUiState(bool isEnabled)
{
StartButton.IsEnabled = isEnabled;
AddFilesButton.IsEnabled = isEnabled;
ClearListButton.IsEnabled = isEnabled;
ScanFilesButton.IsEnabled = isEnabled;
SaveMapButton.IsEnabled = isEnabled;
ClearMapButton.IsEnabled = isEnabled;
LoadMapButton.IsEnabled = isEnabled;
MappingDataGrid.IsEnabled = isEnabled;
OpenOutputButton.IsEnabled = isProcessingComplete || isEnabled;
}
private void Log(string message, Brush color = null)
{
var run = new Run(DateTime.Now.ToString("HH:mm:ss") + " - " + message + Environment.NewLine) { Foreground = color ?? Brushes.Black };
LogTextBlock.Inlines.Add(run);
LogScrollViewer.ScrollToEnd();
}
private void AddFilesButton_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog { Filter = "DRFX Files (*.drfx)|*.drfx", Multiselect = true, Title = "选择 DRFX 文件" };
if (ofd.ShowDialog() == true)
foreach (string f in ofd.FileNames)
if (!FileListBox.Items.Contains(f))
FileListBox.Items.Add(f);
}
private void ClearListButton_Click(object sender, RoutedEventArgs e)
{
FileListBox.Items.Clear();
Log("文件列表已清空。");
}
private void OpenOutputButton_Click(object sender, RoutedEventArgs e)
{
if (string.IsNullOrEmpty(lastOutputDirectory) || !Directory.Exists(lastOutputDirectory))
{
MessageBox.Show("输出目录不存在。请先处理至少一个文件。", "提示");
return;
}
try
{
Process.Start("explorer.exe", lastOutputDirectory);
}
catch (Exception ex)
{
MessageBox.Show("无法打开目录: " + ex.Message, "错误");
}
}
public struct ProgressReport
{
public int Percentage { get; set; }
public string Message { get; set; }
public Brush Color { get; set; }
}
#endregion
private void BrowserOutputPathButton_Click(object sender, RoutedEventArgs e)
{
var dlg = new VistaFolderBrowserDialog()
{
Multiselect=false
};
if (dlg.ShowDialog()== true)
{
OutputPath.Text = dlg.SelectedPath;
}
}
}
}