using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Drawing.Text; using System.Runtime.InteropServices; using System.Runtime.Versioning; using MeetingAssistant.Recording; namespace MeetingAssistant.Taskbar; [SupportedOSPlatform("windows")] internal static class TaskbarIconRenderer { private const int IconSize = 16; private const float GlyphPixelGridOffsetX = -0.5f; public static Icon CreateIcon(RecordingProcessState state) { using var bitmap = RenderBitmap(state); var handle = bitmap.GetHicon(); try { return (Icon)Icon.FromHandle(handle).Clone(); } finally { DestroyIcon(handle); } } internal static Bitmap RenderBitmap(RecordingProcessState state) { var (color, text) = state switch { RecordingProcessState.Recording => (Color.FromArgb(220, 38, 38), "R"), RecordingProcessState.Summarizing => (Color.FromArgb(37, 99, 235), "S"), _ => (Color.FromArgb(75, 85, 99), "I") }; var bitmap = new Bitmap(IconSize, IconSize, PixelFormat.Format32bppArgb); using var graphics = Graphics.FromImage(bitmap); graphics.Clear(Color.Transparent); graphics.SmoothingMode = SmoothingMode.AntiAlias; graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit; using var brush = new SolidBrush(color); graphics.FillEllipse(brush, 1, 1, 14, 14); DrawCenteredGlyph(graphics, text); return bitmap; } private static void DrawCenteredGlyph(Graphics graphics, string text) { using var path = new GraphicsPath(); using var format = StringFormat.GenericTypographic; using var fontFamily = CreateGlyphFontFamily(out var fontStyle); path.AddString( text, fontFamily, (int)fontStyle, 9, Point.Empty, format); var bounds = path.GetBounds(); using var transform = new Matrix(); transform.Translate( (IconSize - bounds.Width) / 2 - bounds.Left + GlyphPixelGridOffsetX, (IconSize - bounds.Height) / 2 - bounds.Top); path.Transform(transform); using var textBrush = new SolidBrush(Color.White); graphics.FillPath(textBrush, path); } private static FontFamily CreateGlyphFontFamily(out FontStyle fontStyle) { foreach (var name in new[] { "Segoe UI", "Arial", "DejaVu Sans", "Liberation Sans", "Noto Sans" }) { try { var family = new FontFamily(name); if (TryGetUsableStyle(family, out fontStyle)) { return family; } family.Dispose(); } catch (ArgumentException) { } } using var installedFonts = new InstalledFontCollection(); foreach (var installedFamily in installedFonts.Families) { try { var family = new FontFamily(installedFamily.Name); if (TryGetUsableStyle(family, out fontStyle)) { return family; } family.Dispose(); } catch (ArgumentException) { } } fontStyle = FontStyle.Bold; return FontFamily.GenericSansSerif; } private static bool TryGetUsableStyle(FontFamily family, out FontStyle fontStyle) { if (family.IsStyleAvailable(FontStyle.Bold)) { fontStyle = FontStyle.Bold; return true; } if (family.IsStyleAvailable(FontStyle.Regular)) { fontStyle = FontStyle.Regular; return true; } fontStyle = FontStyle.Regular; return false; } [DllImport("user32.dll", SetLastError = true)] private static extern bool DestroyIcon(IntPtr hIcon); }