using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Text; namespace MultiWheelC.TrajectoryPlanning.PathSmoothing.Output.Visualization; /// 按准确族名解析报告所需字体,并提供混合中英文文本的共同基线度量。 public sealed class SmoothingFontResolver { public bool TryResolve( string chineseFamilyName, string latinFamilyName, out SmoothingFontResolution resolution, out string reason) { resolution = null; reason = string.Empty; if (string.IsNullOrWhiteSpace(chineseFamilyName) || string.IsNullOrWhiteSpace(latinFamilyName)) { reason = "中文和拉丁字体族名均不能为空。"; return false; } var collection = new InstalledFontCollection(); FontFamily chinese = FindExact(collection.Families, chineseFamilyName); FontFamily latin = FindExact(collection.Families, latinFamilyName); if (chinese == null || latin == null) { collection.Dispose(); reason = "未安装报告所需的精确字体族:" + (chinese == null ? chineseFamilyName : latinFamilyName) + "。"; return false; } resolution = new SmoothingFontResolution(collection, chinese, latin, chineseFamilyName, latinFamilyName); return true; } public RectangleF MeasureMixedText(SmoothingFontResolution resolution, string text, float points) { if (resolution == null) throw new ArgumentNullException(nameof(resolution)); if (string.IsNullOrEmpty(text) || points <= 0f) return RectangleF.Empty; using (var bitmap = new Bitmap(1, 1)) using (Graphics graphics = Graphics.FromImage(bitmap)) using (var format = (StringFormat)StringFormat.GenericTypographic.Clone()) { format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces; float width = 0f, height = 0f; foreach (TextRun run in SplitRuns(text)) { using (Font font = resolution.CreateFont(run.IsChinese ? resolution.ChineseFamily : resolution.LatinFamily, points)) { SizeF size = graphics.MeasureString(run.Text, font, PointF.Empty, format); width += size.Width; height = Math.Max(height, size.Height); } } return new RectangleF(0f, 0f, width, height); } } internal static IReadOnlyList SplitRuns(string text) { var runs = new List(); if (string.IsNullOrEmpty(text)) return runs; int start = 0; bool isChinese = IsChinese(text[0]); for (int index = 1; index < text.Length; index++) { bool currentIsChinese = IsChinese(text[index]); if (currentIsChinese == isChinese) continue; runs.Add(new TextRun(text.Substring(start, index - start), isChinese)); start = index; isChinese = currentIsChinese; } runs.Add(new TextRun(text.Substring(start), isChinese)); return runs; } private static FontFamily FindExact(IReadOnlyList families, string name) { for (int index = 0; index < families.Count; index++) { FontFamily family = families[index]; if (string.Equals(family.Name, name, StringComparison.Ordinal) || string.Equals(family.GetName(1033), name, StringComparison.Ordinal)) return family; } return null; } private static bool IsChinese(char value) { return (value >= 0x3400 && value <= 0x4dbf) || (value >= 0x4e00 && value <= 0x9fff) || (value >= 0xf900 && value <= 0xfaff) || value == 0x3002 || value == 0xff0c || value == 0xff1a; } internal sealed class TextRun { public TextRun(string text, bool isChinese) { Text = text; IsChinese = isChinese; } public string Text { get; } public bool IsChinese { get; } } } /// 一次报告导出所使用的精确字体族;释放时同时释放字体集合。 public sealed class SmoothingFontResolution : IDisposable { private readonly InstalledFontCollection _collection; private readonly string _chineseFamilyName; private readonly string _latinFamilyName; internal SmoothingFontResolution( InstalledFontCollection collection, FontFamily chineseFamily, FontFamily latinFamily, string chineseFamilyName, string latinFamilyName) { _collection = collection ?? throw new ArgumentNullException(nameof(collection)); ChineseFamily = chineseFamily ?? throw new ArgumentNullException(nameof(chineseFamily)); LatinFamily = latinFamily ?? throw new ArgumentNullException(nameof(latinFamily)); _chineseFamilyName = chineseFamilyName ?? throw new ArgumentNullException(nameof(chineseFamilyName)); _latinFamilyName = latinFamilyName ?? throw new ArgumentNullException(nameof(latinFamilyName)); } public string ChineseFamilyName => _chineseFamilyName; public string LatinFamilyName => _latinFamilyName; internal FontFamily ChineseFamily { get; } internal FontFamily LatinFamily { get; } internal Font CreateFont(FontFamily family, float points) { return new Font(family, points, FontStyle.Regular, GraphicsUnit.Point); } public void Dispose() { _collection.Dispose(); } }