diff --git a/src/SheetMe.Designer/Controls/InstalledFontCatalog.cs b/src/SheetMe.Designer/Controls/InstalledFontCatalog.cs
new file mode 100644
index 0000000..22e320d
--- /dev/null
+++ b/src/SheetMe.Designer/Controls/InstalledFontCatalog.cs
@@ -0,0 +1,76 @@
+using System.Globalization;
+using System.Windows.Markup;
+using System.Windows.Media;
+
+namespace SheetMe.Designer.Controls;
+
+///
+/// 이 PC 에 설치된 글꼴 이름 목록 — 인스펙터 글꼴 칸의 선택지.
+///
+/// 한글 이름을 낸다. 레거시가 저장하는 값이 그것이다 — 실사용 서식의 Font 속성은
+/// "굴림, 11.25pt, style=Bold" 처럼 GDI 가 주는 지역화된 이름을 담고 있다.
+/// WPF 의 는 같은 글꼴을 "Gulim" 으로 주므로,
+/// 그걸 그대로 목록에 쓰면 사용자가 고른 순간 서식의 글꼴 이름이 영문으로 바뀐다.
+/// 레거시가 그 이름을 못 찾으면 기본 글꼴로 떨어지고, 그 사실은 EMR 에서만 드러난다.
+///
+/// 목록에 없는 이름도 지운다는 뜻이 아니다. 병원 PC 와 이 PC 의 설치 글꼴은 다르다.
+/// 서식이 쓰는 글꼴이 여기 없을 수 있고, 그건 서식이 틀렸다는 뜻이 아니라
+/// 지금 이 화면이 병원과 다르게 보인다는 뜻이다 — 그래서 값을 고치지 않고 알리기만 한다.
+///
+public static class InstalledFontCatalog
+{
+ #region Member Fields
+ private static IReadOnlyList? cached;
+ private static HashSet? installed;
+ #endregion
+
+ #region Methods
+ /// 설치된 글꼴 이름(한글 우선) — 가나다·알파벳 순, 한 번만 훑는다
+ public static IReadOnlyList Families()
+ {
+ if (cached is not null)
+ {
+ return cached;
+ }
+ var names = new SortedSet(StringComparer.Create(CultureInfo.GetCultureInfo("ko-KR"), ignoreCase: true));
+ foreach (var family in Fonts.SystemFontFamilies)
+ {
+ names.Add(DisplayNameOf(family));
+ }
+ cached = names.ToList();
+ installed = new HashSet(cached, StringComparer.OrdinalIgnoreCase);
+ return cached;
+ }
+
+ /// 이 PC 에 있는 글꼴인가 — 없으면 화면과 병원 출력이 다르다는 신호다
+ public static bool IsInstalled(string family)
+ {
+ if (family.Length == 0)
+ {
+ return true; // 미지정(상속)은 경고 대상이 아니다
+ }
+ Families();
+ return installed!.Contains(family);
+ }
+
+ ///
+ /// 이 글꼴의 표시 이름 — 한국어 이름이 있으면 그것, 없으면 원래 이름.
+ ///
+ /// FamilyNames 는 글꼴이 스스로 담고 있는 언어별 이름표다. 한국어 글꼴은
+ /// ko-KR 항목에 "굴림"·"맑은 고딕" 을 갖고 있고, 레거시(GDI)가 쓰는 이름이 바로 그것이다.
+ ///
+ private static string DisplayNameOf(FontFamily family)
+ {
+ var korean = XmlLanguage.GetLanguage("ko-KR");
+ if (family.FamilyNames.TryGetValue(korean, out var name) && name.Length > 0)
+ {
+ return name;
+ }
+ // Source 는 "Gulim" 처럼 영문이거나, 조합 글꼴이면 "굴림, Arial" 같은 목록일 수 있다 —
+ // 목록이면 첫 이름만 쓴다(레거시 Font.Name 도 하나의 이름이다)
+ var source = family.Source;
+ var comma = source.IndexOf(',');
+ return comma < 0 ? source : source[..comma].Trim();
+ }
+ #endregion
+}
diff --git a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs
index 5a71b9e..9e3917d 100644
--- a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs
+++ b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs
@@ -1095,6 +1095,55 @@ public static class EditSmoke
$"ValueText='{choiceRow.ValueText}' IsMixed={choiceRow.IsMixed}");
}
+ // 15-bb) 글꼴은 목록에서 고른다 — 전에는 자유 입력뿐이라 매번 타이핑해야 했고,
+ // 한 글자만 틀려도 레거시가 아무 말 없이 기본 글꼴로 떨어뜨렸다.
+ //
+ // 이름의 형태가 핵심이다. 레거시가 저장하는 값은 GDI 가 주는 한글 이름
+ // ("굴림, 11.25pt, style=Bold")인데, WPF 의 FontFamily.Source 는 같은 글꼴을
+ // "Gulim" 으로 준다. 영문 이름을 목록에 쓰면 고르는 순간 서식의 글꼴 이름이 바뀌고,
+ // 레거시가 그것을 못 찾으면 조용히 기본 글꼴이 된다 — EMR 에서만 드러난다.
+ {
+ var families = Controls.InstalledFontCatalog.Families();
+ Check("글꼴: 설치 글꼴 목록을 낸다", families.Count > 0, $"{families.Count}종");
+ // 실사용 서식이 쓰는 이름이 목록에 그대로 있어야 한다(sheetdesign_sample.xml 실측: 굴림·돋움)
+ Check("글꼴: 레거시가 저장하는 한글 이름으로 낸다",
+ families.Contains("굴림", StringComparer.Ordinal)
+ || families.Contains("돋움", StringComparer.Ordinal)
+ || families.Contains("맑은 고딕", StringComparer.Ordinal),
+ string.Join(", ", families.Take(8)));
+ Check("글꼴: 설치 여부를 판정한다",
+ Controls.InstalledFontCatalog.IsInstalled(families[0])
+ && !Controls.InstalledFontCatalog.IsInstalled("이런글꼴은없다"),
+ families[0]);
+ // 미지정(상속)은 경고 대상이 아니다 — 대부분의 컨트롤이 Font 키를 갖지 않는다
+ Check("글꼴: 미지정은 경고하지 않는다", Controls.InstalledFontCatalog.IsInstalled(string.Empty));
+
+ var fontDoc = new DesignerViewModel(new FormDesignDataBusiness().CreateNew());
+ fontDoc.AddControlAt("Label", new Point(60, 60));
+ fontDoc.Selection.SetSingle(fontDoc.Pages[0].Controls[0]);
+ var familyRow = fontDoc.Inspector.Rows
+ .OfType().FirstOrDefault(r => r.Label == "글꼴");
+ Check("글꼴: 인스펙터 글꼴 칸이 선택 목록을 가진다",
+ familyRow is not null && familyRow.Families.Count > 0,
+ familyRow is null ? "행 없음" : $"{familyRow.Families.Count}종");
+ if (familyRow is not null)
+ {
+ familyRow.ValueText = "돋움";
+ var saved = fontDoc.Pages[0].Controls[0].Model.Props.GetText("Font") ?? string.Empty;
+ Check("글꼴: 고른 이름이 레거시 형식으로 저장된다",
+ saved.StartsWith("돋움,", StringComparison.Ordinal), saved);
+
+ // 이 PC 에 없는 글꼴은 값을 고치지 않고 알리기만 한다 —
+ // 병원 PC 와 설치 글꼴이 다른 것이 정상이고, 고치면 서식이 망가진다
+ familyRow.ValueText = "이런글꼴은없다";
+ Check("글꼴: 없는 글꼴도 값은 그대로 저장한다",
+ (fontDoc.Pages[0].Controls[0].Model.Props.GetText("Font") ?? string.Empty)
+ .StartsWith("이런글꼴은없다,", StringComparison.Ordinal),
+ fontDoc.Pages[0].Controls[0].Model.Props.GetText("Font") ?? "(없음)");
+ Check("글꼴: 없는 글꼴이면 화면이 병원과 다르다고 알린다", familyRow.IsMissingFont);
+ }
+ }
+
// 15-c) 여러 개를 함께 고르고 공통 속성을 한 번에 바꾼다.
//
// 전에는 타입이 하나라도 다르면 타입 전용 섹션이 통째로 사라졌다. 그런데 여러 개를
@@ -1630,10 +1679,14 @@ public static class EditSmoke
bare.Pages[0].PaperBrush is System.Windows.Media.SolidColorBrush { Color.R: 255, Color.G: 255, Color.B: 255 });
}
- // 기본 글꼴 — 명시 Font 가 없는 컨트롤이 상속한다
- var fontRow = pg.Inspector.Rows.OfType()
+ // 기본 글꼴 — 명시 Font 가 없는 컨트롤이 상속한다.
+ // 컨트롤 글꼴과 같은 선택 목록을 쓴다(전에는 페이지 쪽만 자유 입력으로 남아 있었다).
+ var fontRow = pg.Inspector.Rows.OfType()
.FirstOrDefault(r => r.Label == "글꼴");
Check("페이지 속성: 기본 글꼴 행 존재", fontRow is not null);
+ Check("페이지 속성: 기본 글꼴도 목록에서 고른다",
+ fontRow is not null && fontRow.Families.Count > 0,
+ fontRow is null ? "행 없음" : $"{fontRow.Families.Count}종");
if (fontRow is not null)
{
fontRow.ValueText = "돋움";
diff --git a/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs b/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs
index 238d384..c5118d9 100644
--- a/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs
+++ b/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs
@@ -575,7 +575,8 @@ public sealed class InspectorViewModel : ViewModelBase
// 페이지 글꼴은 자식이 상속한다 — 여기를 바꾸면 Font 를 명시하지 않은 컨트롤이 전부 따라온다
AddSection("기본 글꼴 (컨트롤이 상속)");
- AddPageFontRow(page, "글꼴", f => f.Family, (f, v) => f.Family = v.Length == 0 ? f.Family : v);
+ AddPageFontRow(page, "글꼴", f => f.Family, (f, v) => f.Family = v.Length == 0 ? f.Family : v,
+ isFamily: true);
AddPageFontRow(page, "크기(pt)",
f => f.SizePt.ToString("0.##", CultureInfo.InvariantCulture),
(f, v) => f.SizePt = double.TryParse(v, NumberStyles.Number, CultureInfo.InvariantCulture, out var size) && size > 0
@@ -673,9 +674,12 @@ public sealed class InspectorViewModel : ViewModelBase
return row;
}
- /// 페이지 기본 글꼴 — 자식 상속 컨텍스트가 바뀌므로 문서 전체 시각을 다시 해석한다
+ ///
+ /// 페이지 기본 글꼴 — 자식 상속 컨텍스트가 바뀌므로 문서 전체 시각을 다시 해석한다.
+ /// 면 이름 칸이라 설치 글꼴 목록에서 고를 수 있게 한다.
+ ///
private void AddPageFontRow(PageViewModel page, string label,
- Func get, Action set)
+ Func get, Action set, bool isFamily = false)
{
var root = page.Model.Root;
LegacyFont Current()
@@ -684,7 +688,8 @@ public sealed class InspectorViewModel : ViewModelBase
return text is not null ? LegacyFormat.ParseFont(text) : new LegacyFont();
}
- AddPageRow(page, new TextRowViewModel(label), () => get(Current()), value =>
+ PropertyRowViewModel row = isFamily ? NewFontFamilyRow(label) : new TextRowViewModel(label);
+ AddPageRow(page, row, () => get(Current()), value =>
{
var font = Current();
set(font, value);
@@ -1034,9 +1039,16 @@ public sealed class InspectorViewModel : ViewModelBase
return row;
}
+ ///
+ /// 글꼴 이름 행 — 설치된 글꼴에서 고른다(직접 입력도 그대로 된다).
+ ///
+ /// 전에는 자유 입력 칸 하나뿐이라 "돋움"을 매번 타이핑해야 했다. 그리고 한 글자만 틀려도
+ /// 레거시는 아무 말 없이 기본 글꼴로 떨어진다 — 디자이너에서는 멀쩡해 보이고
+ /// EMR 에서만 다른 글꼴로 나온다.
+ ///
private void AddFontRow(string label, Func get, Action set)
{
- AddRow(new TextRowViewModel(label), new RowBinding
+ AddRow(NewFontFamilyRow(label), new RowBinding
{
Get = vm => get(vm.EffectiveFont),
Set = (vm, value) =>
@@ -1101,6 +1113,16 @@ public sealed class InspectorViewModel : ViewModelBase
return new SegmentItem(icon, label, toggle);
}
+ ///
+ /// 글꼴 이름 행 생성 — 컨트롤 글꼴과 페이지 기본 글꼴이 같은 것을 쓴다.
+ ///
+ /// 목록은 한글 이름이다. 레거시가 저장하는 값이 그것이라("굴림, 11.25pt")
+ /// 영문 이름을 쓰면 고르는 순간 서식의 글꼴 이름이 바뀌고, 레거시가 그것을 못 찾으면
+ /// 조용히 기본 글꼴로 떨어진다.
+ ///
+ private static FontFamilyRowViewModel NewFontFamilyRow(string label)
+ => new(label, SheetMe.Designer.Controls.InstalledFontCatalog.Families(), SheetMe.Designer.Controls.InstalledFontCatalog.IsInstalled);
+
/// 편집 기준 폰트 — 명시 Font 있으면 그 값, 없으면 유효(상속) 폰트 복사본을 물화
private static LegacyFont CurrentFontOf(ControlViewModel vm)
{
diff --git a/src/SheetMe.Designer/ViewModels/Inspector/PropertyRows.cs b/src/SheetMe.Designer/ViewModels/Inspector/PropertyRows.cs
index 20b4d6c..08d9b59 100644
--- a/src/SheetMe.Designer/ViewModels/Inspector/PropertyRows.cs
+++ b/src/SheetMe.Designer/ViewModels/Inspector/PropertyRows.cs
@@ -402,6 +402,47 @@ public sealed class ToggleRowViewModel : PropertyRowViewModel
protected override void OnValueApplied() => OnPropertyChanged(nameof(IsOn));
}
+///
+/// 글꼴 이름 행 — 설치된 글꼴 목록에서 고르거나 직접 입력한다.
+///
+/// 왜 자유 입력만으로는 안 되나. 전에는 그냥 텍스트 칸이라 "돋움"을 매번 타이핑해야 했고,
+/// 한 글자만 틀려도 조용히 기본 글꼴로 떨어졌다(레거시는 못 찾은 이름에 대해 아무 말도 하지 않는다).
+///
+/// 왜 목록만으로도 안 되나. 병원 PC 와 이 PC 의 설치 글꼴이 다르다. 목록에 가두면
+/// 여기 없는 글꼴을 쓰는 서식을 열었을 때 그 값을 고를 수 없게 되고, 손대는 순간 사라진다.
+/// 그래서 고르기도 되고 쓰기도 되는 칸이다.
+///
+public sealed class FontFamilyRowViewModel : PropertyRowViewModel
+{
+ private readonly Func isInstalled;
+
+ /// 선택지 — 이 PC 에 설치된 글꼴
+ public IReadOnlyList Families { get; }
+
+ ///
+ /// 지금 값이 이 PC 에 없는 글꼴인가 — 그러면 이 화면이 병원과 다르게 보인다.
+ /// 서식이 틀린 것이 아니므로 값은 건드리지 않고 알리기만 한다.
+ ///
+ public bool IsMissingFont => !IsMixed && ValueText.Length > 0 && !isInstalled(ValueText);
+
+ /// 경고 문구 — 툴팁으로 보인다
+ public string MissingHint => $"'{ValueText}' 은(는) 이 PC 에 설치되어 있지 않습니다. "
+ + "저장되는 값은 그대로지만, 화면에는 대체 글꼴로 그려집니다.";
+
+ public FontFamilyRowViewModel(string label, IReadOnlyList families, Func isInstalled)
+ : base(label)
+ {
+ Families = families;
+ this.isInstalled = isInstalled;
+ }
+
+ protected override void OnValueApplied()
+ {
+ OnPropertyChanged(nameof(IsMissingFont));
+ OnPropertyChanged(nameof(MissingHint));
+ }
+}
+
/// 선택지 행
public sealed class ChoiceRowViewModel : PropertyRowViewModel
{
diff --git a/src/SheetMe.Designer/Views/InspectorView.xaml b/src/SheetMe.Designer/Views/InspectorView.xaml
index 1cbb3bb..c9133cf 100644
--- a/src/SheetMe.Designer/Views/InspectorView.xaml
+++ b/src/SheetMe.Designer/Views/InspectorView.xaml
@@ -391,6 +391,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+