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 _systemFontDict; public static void Initialize(Dictionary 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 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(); 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 FullMappings { get; set; } = new ObservableCollection(); public List SystemFonts { get; set; } = new List(); 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(); 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>(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().ToList(); SetUiState(false); LogTextBlock.Text = ""; MainProgressBar.Value = 0; isProcessingComplete = false; try { var progress = new Progress(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, List>(); try { await Task.Run(() => { foreach (var drfxFile in FileListBox.Items.Cast()) { 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(); } 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 files, string outputDir, List mappings, IProgress 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 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)(?!)))\})"; 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 + @"(?[^""]*)"""; } 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> ExtractFontAndStylePairs(string content) { var pairs = new List>(); string toolBlockPattern = @"\w+\s*=\s*(?:TextPlus)\s*(\{((?>[^{}]+|\{(?)|\}(?<-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(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; } } } }