595 lines
23 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
} |