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());
}
}