using System.Globalization; using System.Text; using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; namespace SheetMe.Designer.Controls; /// /// Lucide(ISC) 아이콘을 WPF 로 렌더 — SVG(24x24, stroke 2, round) inner 마크업을 /// Geometry 로 파싱해 Viewbox+Path 로 그린다. 외부 패키지 불필요. /// public static class LucideIcons { private static readonly Dictionary Data = new(StringComparer.Ordinal) { ["file-text"] = """ """, ["layout-dashboard"] = """ """, ["pencil-ruler"] = """ """, ["settings"] = """ """, ["eye"] = """ """, ["save"] = """ """, ["folder-open"] = """""", ["plus"] = """ """, ["trash-2"] = """ """, ["link"] = """ """, ["sigma"] = """""", ["table"] = """ """, ["printer"] = """ """, ["pen-line"] = """ """, ["building-2"] = """ """, ["package"] = """ """, ["key-round"] = """ """, ["scroll-text"] = """ """, ["search"] = """ """, ["circle-check"] = """ """, ["type"] = """ """, ["text-cursor-input"] = """ """, ["calendar"] = """ """, ["list"] = """ """, ["square-check"] = """ """, ["circle-dot"] = """ """, ["heading"] = """ """, ["check"] = """""", ["chevron-right"] = """""", ["chevron-left"] = """""", ["chevron-down"] = """""", ["layout-template"] = """ """, ["download"] = """ """, ["pencil"] = """ """, ["folder"] = """""", ["minus"] = """""", ["image"] = """ """, ["mouse-pointer-click"] = """ """, ["list-checks"] = """ """, ["box"] = """ """, ["align-left"] = """ """, ["align-center"] = """ """, ["align-right"] = """ """, ["bold"] = """ """, ["italic"] = """ """, ["underline"] = """ """, ["zoom-in"] = """ """, ["zoom-out"] = """ """, ["maximize"] = """ """, ["eye-off"] = """ """, ["copy"] = """ """, ["clipboard"] = """ """, ["undo-2"] = """ """, ["redo-2"] = """ """, ["sun"] = """ """, ["moon"] = """""", ["move-horizontal"] = """ """, ["move-vertical"] = """ """, ["align-obj-top"] = """ """, ["align-obj-middle"] = """ """, ["align-obj-bottom"] = """ """, ["distribute-h"] = """ """, ["distribute-v"] = """ """, ["square"] = """""", ["lock"] = """ """, ["unlock"] = """ """, ["group"] = """ """, ["ungroup"] = """ """, ["align-obj-left"] = """ """, ["align-obj-center-h"] = """ """, ["align-obj-right"] = """ """, ["droplet"] = """""", ["rotate-cw"] = """ """, ["frame"] = """ """, ["filter"] = """""", ["filter-x"] = """ """, ["user"] = """ """, ["play"] = """""", ["menu"] = """ """, ["hand"] = """ """, ["layout-grid"] = """ """, }; /// 아이콘 + 텍스트 가로 묶음 (버튼/네비 Content 용). public static StackPanel Label(string icon, string text, double iconSize = 15, Brush? color = null) { var sp = new StackPanel { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center }; sp.Children.Add(Icon(icon, iconSize, color)); if (!string.IsNullOrEmpty(text)) { var tb = new TextBlock { Text = text, Margin = new Thickness(6, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center }; if (color is not null) tb.Foreground = color; // 아이콘과 같은 색을 글자에도 적용(다크 배경서 글자 안 묻히게) sp.Children.Add(tb); } return sp; } public static FrameworkElement Icon(string name, double size = 16, Brush? color = null) { color ??= Brushes.Black; var canvas = new Canvas { Width = 24, Height = 24 }; if (Data.TryGetValue(name, out var markup)) { var (stroke, fill) = Parse(markup); if (stroke.Children.Count > 0) canvas.Children.Add(new Path { Data = stroke, Stroke = color, StrokeThickness = 2, StrokeStartLineCap = PenLineCap.Round, StrokeEndLineCap = PenLineCap.Round, StrokeLineJoin = PenLineJoin.Round }); if (fill.Children.Count > 0) canvas.Children.Add(new Path { Data = fill, Fill = color }); } return new Viewbox { Width = size, Height = size, Child = canvas, Stretch = Stretch.Uniform, SnapsToDevicePixels = true }; } private static (GeometryGroup Stroke, GeometryGroup Fill) Parse(string markup) { var stroke = new GeometryGroup(); var fill = new GeometryGroup(); foreach (Match m in Regex.Matches(markup, @"<(path|rect|circle|line|polyline|polygon)\b([^>]*?)/?>", RegexOptions.Singleline)) { var tag = m.Groups[1].Value; var attrs = m.Groups[2].Value; var isFill = string.Equals(Attr(attrs, "fill"), "currentColor", StringComparison.OrdinalIgnoreCase); Geometry? g = tag switch { "path" => SafeParse(Attr(attrs, "d")), "rect" => new RectangleGeometry(new Rect(D(attrs, "x"), D(attrs, "y"), D(attrs, "width"), D(attrs, "height")), D(attrs, "rx"), D(attrs, "rx")), "circle" => new EllipseGeometry(new Point(D(attrs, "cx"), D(attrs, "cy")), D(attrs, "r"), D(attrs, "r")), "line" => new LineGeometry(new Point(D(attrs, "x1"), D(attrs, "y1")), new Point(D(attrs, "x2"), D(attrs, "y2"))), "polyline" => PolyGeo(Attr(attrs, "points"), false), "polygon" => PolyGeo(Attr(attrs, "points"), true), _ => null, }; if (g is not null) (isFill ? fill : stroke).Children.Add(g); } return (stroke, fill); } private static string Attr(string attrs, string name) { var m = Regex.Match(attrs, name + @"\s*=\s*""([^""]*)"""); return m.Success ? m.Groups[1].Value : string.Empty; } private static double D(string attrs, string name) => double.TryParse(Attr(attrs, name), NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : 0; private static Geometry? SafeParse(string d) { if (string.IsNullOrWhiteSpace(d)) return null; try { return Geometry.Parse(d); } catch { return null; } } private static Geometry? PolyGeo(string points, bool closed) { var nums = Regex.Split(points.Trim(), @"[\s,]+").Where(s => s.Length > 0) .Select(s => double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : 0).ToList(); if (nums.Count < 4) return null; var sb = new StringBuilder(); for (int i = 0; i + 1 < nums.Count; i += 2) sb.Append(i == 0 ? "M" : " L").Append(' ').Append(nums[i].ToString(CultureInfo.InvariantCulture)).Append(',').Append(nums[i + 1].ToString(CultureInfo.InvariantCulture)); if (closed) sb.Append(" Z"); return SafeParse(sb.ToString()); } }