From d4ba3c1188fa3856315888f7abdc1519e2673f07 Mon Sep 17 00:00:00 2001 From: Johnny Westlake Date: Sun, 22 Oct 2023 22:04:56 +0100 Subject: [PATCH 01/31] Fix #291 --- CharacterMap/CharacterMap/CharacterMap.csproj | 1 + .../CharacterMap/Core/ExportManager.Font.cs | 70 +++++++++++++------ CharacterMap/CharacterMap/Core/FontVariant.cs | 56 ++++++++------- .../CharacterMap/Core/InstalledFont.cs | 16 ++--- CharacterMap/CharacterMap/Core/Utils.cs | 9 +++ .../CharacterMap/GlobalSuppressions.cs | 8 +++ 6 files changed, 104 insertions(+), 56 deletions(-) create mode 100644 CharacterMap/CharacterMap/GlobalSuppressions.cs diff --git a/CharacterMap/CharacterMap/CharacterMap.csproj b/CharacterMap/CharacterMap/CharacterMap.csproj index 30561fa6..09acbe25 100644 --- a/CharacterMap/CharacterMap/CharacterMap.csproj +++ b/CharacterMap/CharacterMap/CharacterMap.csproj @@ -239,6 +239,7 @@ + diff --git a/CharacterMap/CharacterMap/Core/ExportManager.Font.cs b/CharacterMap/CharacterMap/Core/ExportManager.Font.cs index 5105775e..5b5158fb 100644 --- a/CharacterMap/CharacterMap/Core/ExportManager.Font.cs +++ b/CharacterMap/CharacterMap/Core/ExportManager.Font.cs @@ -45,6 +45,11 @@ public static async void RequestExportFontFile(FontVariant variant) WeakReferenceMessenger.Default.Send(new AppNotificationMessage(true, new ExportFontFileResult(null, false))); } + static List> GetGrouped(IList fonts) + { + return fonts.Where(f => DirectWrite.IsFontLocal(f.Face)).GroupBy(f => DirectWrite.GetFileName(f.Face)).ToList(); + } + internal static Task ExportCollectionAsZipAsync( IList fontList, UserFontCollection selectedCollection, @@ -61,29 +66,27 @@ internal static async Task ExportFontsAsZipAsync( { if (await PickFileAsync(name, "ZIP", new[] { ".zip" }) is StorageFile file) { + callback?.Invoke($"0%"); await Task.Run(async () => { ExportNamingScheme scheme = ResourceHelper.AppSettings.ExportNamingScheme; + var grouped = GetGrouped(fonts); using var i = await file.OpenStreamForWriteAsync(); i.SetLength(0); int c = 0; using ZipArchive z = new(i, ZipArchiveMode.Create); - callback?.Invoke($"0%"); - - foreach (var font in fonts) + foreach (var group in grouped) { - if (DirectWrite.IsFontLocal(font.Face)) - { - string fileName = GetFileName(font, scheme); - ZipArchiveEntry entry = z.CreateEntry(fileName); - using IOutputStream s = entry.Open().AsOutputStream(); - await DirectWrite.WriteToStreamAsync(font.Face, s); - } + + string fileName = GetFileName(group, scheme); + ZipArchiveEntry entry = z.CreateEntry(fileName); + using IOutputStream s = entry.Open().AsOutputStream(); + await DirectWrite.WriteToStreamAsync(group.First().Face, s); c++; - callback?.Invoke($"{((double)c / (double)fonts.Count) * 100:0}%"); + callback?.Invoke($"{((double)c / (double)grouped.Count) * 100:0}%"); } await i.FlushAsync(); @@ -107,23 +110,21 @@ internal static async Task ExportFontsToFolderAsync( { if (await PickFolderAsync() is StorageFolder folder) { + callback?.Invoke($"0%"); await Task.Run(async () => { ExportNamingScheme scheme = ResourceHelper.AppSettings.ExportNamingScheme; - callback?.Invoke($"0%"); - int c = 0; - foreach (var font in fonts) + + var grouped = GetGrouped(fonts); ; + foreach (var group in grouped) { - if (DirectWrite.IsFontLocal(font.Face)) - { - string fileName = GetFileName(font, scheme); - StorageFile file = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting).AsTask().ConfigureAwait(false); - await TryWriteToFileAsync(font, file).ConfigureAwait(false); - } + string fileName = GetFileName(group, scheme); + StorageFile file = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting).AsTask().ConfigureAwait(false); + await TryWriteToFileAsync(group.First(), file).ConfigureAwait(false); c++; - callback?.Invoke($"{((double)c / (double)fonts.Count) * 100:0}%"); + callback?.Invoke($"{((double)c / (double)grouped.Count) * 100:0}%"); } }); @@ -148,6 +149,33 @@ private static async Task TryWriteToFileAsync(FontVariant font, StorageFil return false; } + private static string GetFileName(IGrouping group, ExportNamingScheme scheme) + { + string fileName = null; + string ext = ".ttf"; + + var src = group.Key; + if (!string.IsNullOrWhiteSpace(src)) + { + var strsrc = Path.GetExtension(src); + if (!string.IsNullOrWhiteSpace(strsrc)) + ext = strsrc; + } + + if (scheme == ExportNamingScheme.System && !string.IsNullOrWhiteSpace(src)) + fileName = src; + + if (scheme is ExportNamingScheme.Optimised && FontFinder.ImportFormats.Contains(ext) is false) + ext = ".ttf"; + + var font = Utils.GetDefaultVariant(group.ToList()); + + if (string.IsNullOrWhiteSpace(fileName)) + fileName = $"{font.GetFullName().Trim()}{ext}"; + + return $"{Utils.Humanise(Path.GetFileNameWithoutExtension(fileName), false)}{Path.GetExtension(fileName).ToLower()}"; + } + private static string GetFileName(FontVariant font, ExportNamingScheme scheme) { string fileName = null; diff --git a/CharacterMap/CharacterMap/Core/FontVariant.cs b/CharacterMap/CharacterMap/Core/FontVariant.cs index e623c74b..5cfdddb1 100644 --- a/CharacterMap/CharacterMap/Core/FontVariant.cs +++ b/CharacterMap/CharacterMap/Core/FontVariant.cs @@ -11,6 +11,8 @@ namespace CharacterMap.Core { + public record FaceMetadataInfo(string Key, string Value, CanvasFontInformation Info); + [System.Diagnostics.DebuggerDisplay("{FamilyName} {PreferredName}")] public partial class FontVariant : IDisposable { @@ -18,12 +20,12 @@ public partial class FontVariant : IDisposable private static Dictionary _characters { get; } = new (); private IReadOnlyList _ranges = null; - private IReadOnlyList> _fontInformation = null; + private IReadOnlyList _fontInformation = null; private IReadOnlyList _typographyFeatures = null; private IReadOnlyList _xamlTypographyFeatures = null; private FontAnalysis _analysis = null; - public IReadOnlyList> FontInformation + public IReadOnlyList FontInformation => _fontInformation ??= LoadFontInformation(); public IReadOnlyList TypographyFeatures @@ -79,7 +81,7 @@ public IReadOnlyList XamlTypographyFeatures public string Source { get; } /// - /// A FontFamily source for XAML that includes a custom fallback font. + /// A FontFamily source for XAML that includes a custom fall-back font. /// This results in XAML *only* rendering the characters included in the font. /// Use when you may have a scenario where characters not inside a font's glyph /// range might be displayed, otherwise use for better performance. @@ -203,7 +205,15 @@ private FontAnalysis GetAnalysisInternal() public string TryGetSampleText() { - return GetInfoKey(Face, CanvasFontInformation.SampleText).Value; + return GetInfoKey(Face, CanvasFontInformation.SampleText)?.Value; + } + + public string GetFullName() + { + if (GetInfo(CanvasFontInformation.FullName) is { } info) + return info.Value; + + return this.PreferredName; } private void LoadTypographyFeatures() @@ -220,37 +230,35 @@ private void LoadTypographyFeatures() _typographyFeatures = features; } - private List> LoadFontInformation() + private List LoadFontInformation() { - //KeyValuePair Get(CanvasFontInformation info) - //{ - // var infos = FontFace.GetInformationalStrings(info); - // if (infos.Count == 0) - // return new KeyValuePair(); - - // var name = info.Humanise(); - // var dic = infos.ToDictionary(k => k.Key, k => k.Value); - // if (infos.TryGetValue(CultureInfo.CurrentCulture.Name, out string value) - // || infos.TryGetValue("en-us", out value)) - // return KeyValuePair.Create(name, value); - // return KeyValuePair.Create(name, infos.First().Value); - //} - - return INFORMATIONS.Select(i => GetInfoKey(Face, i)).Where(s => s.Key != null).ToList(); + return INFORMATIONS.Select(i => GetInfoKey(Face, i)).Where(s => s != null && s.Key != null).ToList(); } - private static KeyValuePair GetInfoKey(DWriteFontFace fontFace, CanvasFontInformation info) + private static FaceMetadataInfo GetInfoKey(DWriteFontFace fontFace, CanvasFontInformation info) { var infos = fontFace.GetInformationalStrings(info); if (infos.Count == 0) - return new(); + return null; var name = info.Humanise(); var dic = infos.ToDictionary(k => k.Key, k => k.Value); if (infos.TryGetValue(CultureInfo.CurrentCulture.Name, out string value) || infos.TryGetValue("en-us", out value)) - return KeyValuePair.Create(name, value); - return KeyValuePair.Create(name, infos.First().Value); + return new (name, value, info); + return new(name, infos.First().Value, info); + } + + private FaceMetadataInfo GetInfo(CanvasFontInformation cfi) + { + if (_fontInformation is not null && _fontInformation.FirstOrDefault(p => p.Info == cfi) + is { } info) + return info; + + if (GetInfoKey(Face, cfi) is { } faceInfo) + return faceInfo; + + return null; } diff --git a/CharacterMap/CharacterMap/Core/InstalledFont.cs b/CharacterMap/CharacterMap/Core/InstalledFont.cs index a7fa3f86..96e8d175 100644 --- a/CharacterMap/CharacterMap/Core/InstalledFont.cs +++ b/CharacterMap/CharacterMap/Core/InstalledFont.cs @@ -23,6 +23,9 @@ public class InstalledFont : IComparable, IEquatable public bool HasImportedFiles { get; private set; } + private FontVariant _defaultVariant; + public FontVariant DefaultVariant => _defaultVariant ??= Utils.GetDefaultVariant(Variants); + private InstalledFont(string name) { Name = name; @@ -34,23 +37,14 @@ public InstalledFont(string name, DWriteFontFace face, StorageFile file = null) AddVariant(face, file); } - public FontVariant DefaultVariant - { - get - { - return Variants.FirstOrDefault(v => v.DirectWriteProperties.Weight.Weight == FontWeights.Normal.Weight && v.DirectWriteProperties.Style == FontStyle.Normal && v.DirectWriteProperties.Stretch == FontStretch.Normal) - ?? Variants.FirstOrDefault(v => v.DirectWriteProperties.Weight.Weight == FontWeights.Normal.Weight && v.DirectWriteProperties.Style == FontStyle.Normal) - ?? Variants.FirstOrDefault(v => v.DirectWriteProperties.Weight.Weight == FontWeights.Normal.Weight && v.DirectWriteProperties.Stretch == FontStretch.Normal) - ?? Variants.FirstOrDefault(v => v.DirectWriteProperties.Weight.Weight == FontWeights.Normal.Weight) - ?? Variants[0]; - } - } public void AddVariant(DWriteFontFace fontFace, StorageFile file = null) { _variants.Add(new (fontFace, file)); if (file != null) HasImportedFiles = true; + + _defaultVariant = null; } public void SortVariants() diff --git a/CharacterMap/CharacterMap/Core/Utils.cs b/CharacterMap/CharacterMap/Core/Utils.cs index 4b830d17..7b733f59 100644 --- a/CharacterMap/CharacterMap/Core/Utils.cs +++ b/CharacterMap/CharacterMap/Core/Utils.cs @@ -129,6 +129,15 @@ public static Task TryCopyToClipboardAsync(string s, FontMapViewModel view return TryCopyToClipboardInternalAsync(s, c, viewModel); } + public static FontVariant GetDefaultVariant(IList variants) + { + return variants.FirstOrDefault(v => v.DirectWriteProperties.Weight.Weight == FontWeights.Normal.Weight && v.DirectWriteProperties.Style == FontStyle.Normal && v.DirectWriteProperties.Stretch == FontStretch.Normal) + ?? variants.FirstOrDefault(v => v.DirectWriteProperties.Weight.Weight == FontWeights.Normal.Weight && v.DirectWriteProperties.Style == FontStyle.Normal) + ?? variants.FirstOrDefault(v => v.DirectWriteProperties.Weight.Weight == FontWeights.Normal.Weight && v.DirectWriteProperties.Stretch == FontStretch.Normal) + ?? variants.FirstOrDefault(v => v.DirectWriteProperties.Weight.Weight == FontWeights.Normal.Weight) + ?? variants[0]; + } + public static async Task TryCopyToClipboardInternalAsync(string rawString, string formattedString, FontMapViewModel viewModel, CopyDataType type = CopyDataType.Text, IRandomAccessStream data = null) { // Internal helper method to set clipboard diff --git a/CharacterMap/CharacterMap/GlobalSuppressions.cs b/CharacterMap/CharacterMap/GlobalSuppressions.cs new file mode 100644 index 00000000..db6cfb0d --- /dev/null +++ b/CharacterMap/CharacterMap/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Naming", "VSSpell001:Spell Check", Justification = "BritishEnglish", Scope = "member", Target = "~P:CharacterMap.Core.FontVariant.SupportsColourRendering")] From 58fd87da6cf73bf3758993dc2bf70c5b7cf1b5b3 Mon Sep 17 00:00:00 2001 From: Johnny Westlake Date: Sun, 22 Oct 2023 22:05:20 +0100 Subject: [PATCH 02/31] Reorder settings menu --- .../CharacterMap/Core/ExportManager.Font.cs | 4 +- CharacterMap/CharacterMap/Core/FontVariant.cs | 129 ++++++------ .../CharacterMap/GlobalSuppressions.cs | 2 + .../CharacterMap/Views/MainPage.xaml.cs | 2 +- .../CharacterMap/Views/SettingsView.xaml | 193 +++++++++--------- 5 files changed, 157 insertions(+), 173 deletions(-) diff --git a/CharacterMap/CharacterMap/Core/ExportManager.Font.cs b/CharacterMap/CharacterMap/Core/ExportManager.Font.cs index 5b5158fb..0ec70275 100644 --- a/CharacterMap/CharacterMap/Core/ExportManager.Font.cs +++ b/CharacterMap/CharacterMap/Core/ExportManager.Font.cs @@ -171,7 +171,7 @@ private static string GetFileName(IGrouping group, ExportNa var font = Utils.GetDefaultVariant(group.ToList()); if (string.IsNullOrWhiteSpace(fileName)) - fileName = $"{font.GetFullName().Trim()}{ext}"; + fileName = $"{font.TryGetFullName().Trim()}{ext}"; return $"{Utils.Humanise(Path.GetFileNameWithoutExtension(fileName), false)}{Path.GetExtension(fileName).ToLower()}"; } @@ -196,7 +196,7 @@ private static string GetFileName(FontVariant font, ExportNamingScheme scheme) ext = ".ttf"; if (string.IsNullOrWhiteSpace(fileName)) - fileName = $"{font.FamilyName.Trim()} {font.PreferredName.Trim()}{ext}"; + fileName = $"{font.TryGetFullName().Trim()}{ext}"; return $"{Utils.Humanise(Path.GetFileNameWithoutExtension(fileName), false)}{Path.GetExtension(fileName).ToLower()}"; } diff --git a/CharacterMap/CharacterMap/Core/FontVariant.cs b/CharacterMap/CharacterMap/Core/FontVariant.cs index 5cfdddb1..a619b6b6 100644 --- a/CharacterMap/CharacterMap/Core/FontVariant.cs +++ b/CharacterMap/CharacterMap/Core/FontVariant.cs @@ -1,4 +1,6 @@ -using CharacterMap.Helpers; +// Ignore Spelling: cfi + +using CharacterMap.Helpers; using CharacterMap.Models; using CharacterMap.Services; using CharacterMapCX; @@ -25,33 +27,16 @@ public partial class FontVariant : IDisposable private IReadOnlyList _xamlTypographyFeatures = null; private FontAnalysis _analysis = null; - public IReadOnlyList FontInformation - => _fontInformation ??= LoadFontInformation(); + public IReadOnlyList FontInformation => _fontInformation ??= GetFontInformation(); - public IReadOnlyList TypographyFeatures - { - get - { - if (_typographyFeatures == null) - LoadTypographyFeatures(); - return _typographyFeatures; - } - } + public IReadOnlyList TypographyFeatures => _typographyFeatures ??= LoadTypographyFeatures(); /// /// Supported XAML typographer features for A SINGLE GLYPH. /// Does not include features like Alternates which are used for strings of text. /// - public IReadOnlyList XamlTypographyFeatures - { - get - { - if (_xamlTypographyFeatures == null) - LoadTypographyFeatures(); - return _xamlTypographyFeatures; - } - } - + public IReadOnlyList XamlTypographyFeatures => _xamlTypographyFeatures ??= LoadTypographyFeatures(true); + public bool HasXamlTypographyFeatures => XamlTypographyFeatures.Count > 0; public CanvasFontFace FontFace => Face.FontFace; @@ -166,30 +151,18 @@ public IReadOnlyList GetCharacters() return Characters; } - public int GetGlyphIndex(Character c) - { - return Face.GetGlyphIndice(c.UnicodeIndex); - } + public int GetGlyphIndex(Character c) => Face.GetGlyphIndice(c.UnicodeIndex); - public uint[] GetGlyphUnicodeIndexes() - { - return GetCharacters().Select(c => c.UnicodeIndex).ToArray(); - } + public uint[] GetGlyphUnicodeIndexes() => GetCharacters().Select(c => c.UnicodeIndex).ToArray(); - public FontAnalysis GetAnalysis() - { - return _analysis ??= TypographyAnalyzer.Analyze(this); - } + public FontAnalysis GetAnalysis() => _analysis ??= TypographyAnalyzer.Analyze(this); /// /// Load an analysis without a glyph search map. Callers later using the cached analysis and expecting a search map should /// take care to ensure it's created by manually calling /// /// - private FontAnalysis GetAnalysisInternal() - { - return _analysis ??= TypographyAnalyzer.Analyze(this, false); - } + private FontAnalysis GetAnalysisInternal() =>_analysis ??= TypographyAnalyzer.Analyze(this, false); /// /// Used temporarily to allow insider builds to access COLRv1. Do not use elsewhere. Very expensive. @@ -203,20 +176,41 @@ private FontAnalysis GetAnalysisInternal() /// /// public bool SupportsColourRendering => Utils.Supports23H2 && DirectWriteProperties.IsColorFont; - public string TryGetSampleText() - { - return GetInfoKey(Face, CanvasFontInformation.SampleText)?.Value; - } + public string TryGetSampleText() => ReadInfoKey(CanvasFontInformation.SampleText)?.Value; + + /// + /// Attempts to return the value of . If it fails, + /// is returned instead. + /// + /// + public string TryGetFullName() => TryGetInfo(CanvasFontInformation.FullName)?.Value ?? PreferredName; + - public string GetFullName() + + + /* SEARCHING */ + + public Dictionary SearchMap { get; set; } + + public string GetDescription(Character c) { - if (GetInfo(CanvasFontInformation.FullName) is { } info) - return info.Value; + if (SearchMap == null + || !SearchMap.TryGetValue(c, out string mapping) + || string.IsNullOrWhiteSpace(mapping)) + return GlyphService.GetCharacterDescription(c.UnicodeIndex, this); - return this.PreferredName; + return GlyphService.TryGetAGLFNName(mapping); } - private void LoadTypographyFeatures() + + + + /* INTERNAL */ + + private List GetFontInformation() + => INFORMATIONS.Select(ReadInfoKey).Where(s => s != null).ToList(); + + private IReadOnlyList LoadTypographyFeatures(bool isXaml = false) { var features = TypographyAnalyzer.GetSupportedTypographyFeatures(this); @@ -228,16 +222,19 @@ private void LoadTypographyFeatures() if (features.Count > 0) features.Insert(0, TypographyFeatureInfo.None); _typographyFeatures = features; - } - private List LoadFontInformation() - { - return INFORMATIONS.Select(i => GetInfoKey(Face, i)).Where(s => s != null && s.Key != null).ToList(); + return isXaml ? _xamlTypographyFeatures : _typographyFeatures; } - private static FaceMetadataInfo GetInfoKey(DWriteFontFace fontFace, CanvasFontInformation info) + /// + /// Reads an info key from the underlying DWriteFontFace + /// + /// + /// + /// + private FaceMetadataInfo ReadInfoKey(CanvasFontInformation info) { - var infos = fontFace.GetInformationalStrings(info); + var infos = Face.GetInformationalStrings(info); if (infos.Count == 0) return null; @@ -249,13 +246,18 @@ private static FaceMetadataInfo GetInfoKey(DWriteFontFace fontFace, CanvasFontIn return new(name, infos.First().Value, info); } - private FaceMetadataInfo GetInfo(CanvasFontInformation cfi) + /// + /// Attempts to return a cached info key, or load it from scratch. + /// + /// + /// + public FaceMetadataInfo TryGetInfo(CanvasFontInformation cfi) { if (_fontInformation is not null && _fontInformation.FirstOrDefault(p => p.Info == cfi) is { } info) return info; - if (GetInfoKey(Face, cfi) is { } faceInfo) + if (ReadInfoKey(cfi) is { } faceInfo) return faceInfo; return null; @@ -264,23 +266,6 @@ private FaceMetadataInfo GetInfo(CanvasFontInformation cfi) - /* SEARCHING */ - - public Dictionary SearchMap { get; set; } - - public string GetDescription(Character c) - { - if (SearchMap == null - || !SearchMap.TryGetValue(c, out string mapping) - || string.IsNullOrWhiteSpace(mapping)) - return GlyphService.GetCharacterDescription(c.UnicodeIndex, this); - - return GlyphService.TryGetAGLFNName(mapping); - } - - - - /* .NET */ public void Dispose() diff --git a/CharacterMap/CharacterMap/GlobalSuppressions.cs b/CharacterMap/CharacterMap/GlobalSuppressions.cs index db6cfb0d..103f047e 100644 --- a/CharacterMap/CharacterMap/GlobalSuppressions.cs +++ b/CharacterMap/CharacterMap/GlobalSuppressions.cs @@ -6,3 +6,5 @@ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Naming", "VSSpell001:Spell Check", Justification = "BritishEnglish", Scope = "member", Target = "~P:CharacterMap.Core.FontVariant.SupportsColourRendering")] +[assembly: SuppressMessage("Naming", "VSSpell001:Spell Check", Justification = "Correct", Scope = "member", Target = "~P:CharacterMap.Core.FontVariant.Panose")] +[assembly: SuppressMessage("Naming", "VSSpell001:Spell Check", Justification = "", Scope = "member", Target = "~P:CharacterMap.ViewModels.SettingsViewModel.Changelog")] diff --git a/CharacterMap/CharacterMap/Views/MainPage.xaml.cs b/CharacterMap/CharacterMap/Views/MainPage.xaml.cs index 4e981ed5..5d977610 100644 --- a/CharacterMap/CharacterMap/Views/MainPage.xaml.cs +++ b/CharacterMap/CharacterMap/Views/MainPage.xaml.cs @@ -708,7 +708,7 @@ public void ShowEditSuggestions() _ = MainDispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () => { await WindowService.ReactivateMainAsync(); - ShowSettings(5); + ShowSettings(4); }); } diff --git a/CharacterMap/CharacterMap/Views/SettingsView.xaml b/CharacterMap/CharacterMap/Views/SettingsView.xaml index 6610b464..871a1206 100644 --- a/CharacterMap/CharacterMap/Views/SettingsView.xaml +++ b/CharacterMap/CharacterMap/Views/SettingsView.xaml @@ -120,45 +120,45 @@ + Content="{x:Bind h:Localization.Get('SettingsCollectionsHeader')}" + Tag="CollectionPanel"> - + - - - - + Content="{x:Bind h:Localization.Get('SettingsFontManagementLabel/Text')}" + Tag="FontPanel" /> + Content="{x:Bind h:Localization.Get('SettingsSuggestionsHeader/Text')}" + Tag="RampPanel"> - + + Content="{x:Bind h:Localization.Get('SettingsExportHeader/Text')}" + Tag="ExportPanel"> - + + Content="{x:Bind h:Localization.Get('SettingsCharacterSearchHeader/Text')}" + Tag="SearchPanel"> + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - + + + + + + + + + + + + + + + + + - - - - - - + + + + - + + + + + + + + + + + + + - - - - + + + From c806473f44e03c4ecb5ac36425d1b02816fa1eb0 Mon Sep 17 00:00:00 2001 From: Johnny Westlake Date: Tue, 24 Oct 2023 13:08:19 +0100 Subject: [PATCH 03/31] Support loading and showing script tags --- CharacterMap/CharacterMap.CX/DWriteFontFace.h | 15 +- CharacterMap/CharacterMap/CharacterMap.csproj | 1 + CharacterMap/CharacterMap/Core/FontFinder.cs | 11 - CharacterMap/CharacterMap/Core/FontVariant.cs | 31 +-- .../CharacterMap/GlobalSuppressions.cs | 19 +- CharacterMap/CharacterMap/Helpers/Unicode.cs | 8 +- .../CharacterMap/Helpers/UnicodeScriptTags.cs | 196 ++++++++++++++++++ .../Provider/SQLiteGlyphProvider.cs | 12 +- .../CharacterMap/Services/GlyphService.cs | 7 - .../CharacterMap/Views/FontMapView.xaml.cs | 12 -- 10 files changed, 246 insertions(+), 66 deletions(-) create mode 100644 CharacterMap/CharacterMap/Helpers/UnicodeScriptTags.cs diff --git a/CharacterMap/CharacterMap.CX/DWriteFontFace.h b/CharacterMap/CharacterMap.CX/DWriteFontFace.h index ca9dbf3b..ad67f3c0 100644 --- a/CharacterMap/CharacterMap.CX/DWriteFontFace.h +++ b/CharacterMap/CharacterMap.CX/DWriteFontFace.h @@ -61,8 +61,7 @@ namespace CharacterMapCX ThrowIfFailed(m_font->GetInformationalStrings( static_cast(fontInformation), &localizedStrings, &exists)); - - if (localizedStrings) + if (exists) { const uint32_t stringCount = localizedStrings->GetCount(); @@ -74,10 +73,16 @@ namespace CharacterMapCX wchar_t* name = new wchar_t[length + 1]; localizedStrings->GetString(i, name, length); + wchar_t* locale; localizedStrings->GetLocaleNameLength(i, &length); - length++; - wchar_t* locale = new wchar_t[length + 1]; - localizedStrings->GetLocaleName(i, locale, length); + if (length == 0) + locale = name; + else + { + length++; + locale = new wchar_t[length + 1]; + localizedStrings->GetLocaleName(i, locale, length); + } ThrowIfFailed(map->Insert( ref new String(locale), ref new String(name))); diff --git a/CharacterMap/CharacterMap/CharacterMap.csproj b/CharacterMap/CharacterMap/CharacterMap.csproj index 09acbe25..82461146 100644 --- a/CharacterMap/CharacterMap/CharacterMap.csproj +++ b/CharacterMap/CharacterMap/CharacterMap.csproj @@ -249,6 +249,7 @@ + diff --git a/CharacterMap/CharacterMap/Core/FontFinder.cs b/CharacterMap/CharacterMap/Core/FontFinder.cs index 92756347..92f05cc3 100644 --- a/CharacterMap/CharacterMap/Core/FontFinder.cs +++ b/CharacterMap/CharacterMap/Core/FontFinder.cs @@ -1,15 +1,4 @@ -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMapCX; -using CommunityToolkit.Mvvm.Messaging; -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; using Windows.Storage; using Windows.Storage.Search; using Windows.UI.Text; diff --git a/CharacterMap/CharacterMap/Core/FontVariant.cs b/CharacterMap/CharacterMap/Core/FontVariant.cs index a619b6b6..9c59ee13 100644 --- a/CharacterMap/CharacterMap/Core/FontVariant.cs +++ b/CharacterMap/CharacterMap/Core/FontVariant.cs @@ -1,19 +1,17 @@ // Ignore Spelling: cfi -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.Services; -using CharacterMapCX; using Microsoft.Graphics.Canvas.Text; -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using Windows.Storage; namespace CharacterMap.Core { - public record FaceMetadataInfo(string Key, string Value, CanvasFontInformation Info); + public record FaceMetadataInfo(string Key, string[] Values, CanvasFontInformation Info) + { + public string Value => string.Join(", ", Values); + } + + [System.Diagnostics.DebuggerDisplay("{FamilyName} {PreferredName}")] public partial class FontVariant : IDisposable @@ -208,7 +206,9 @@ public string GetDescription(Character c) /* INTERNAL */ private List GetFontInformation() - => INFORMATIONS.Select(ReadInfoKey).Where(s => s != null).ToList(); + => INFORMATIONS.Select(ReadInfoKey) + .Where(s => s != null && !string.IsNullOrWhiteSpace(s.Value)) + .ToList(); private IReadOnlyList LoadTypographyFeatures(bool isXaml = false) { @@ -242,8 +242,14 @@ private FaceMetadataInfo ReadInfoKey(CanvasFontInformation info) var dic = infos.ToDictionary(k => k.Key, k => k.Value); if (infos.TryGetValue(CultureInfo.CurrentCulture.Name, out string value) || infos.TryGetValue("en-us", out value)) - return new (name, value, info); - return new(name, infos.First().Value, info); + return new (name, new string[1] { value }, info); + + return new( + name, + info is CanvasFontInformation.DesignScriptLanguageTag + ? infos.Select(i => UnicodeScriptTags.GetName(i.Value)).ToArray() + : infos.Select(i => i.Value).ToArray(), + info); } /// @@ -295,9 +301,10 @@ public static FontVariant CreateDefault(DWriteFontFace face) private static CanvasFontInformation[] INFORMATIONS { get; } = { CanvasFontInformation.FullName, CanvasFontInformation.Description, + CanvasFontInformation.VersionStrings, + CanvasFontInformation.DesignScriptLanguageTag, CanvasFontInformation.Designer, CanvasFontInformation.DesignerUrl, - CanvasFontInformation.VersionStrings, CanvasFontInformation.FontVendorUrl, CanvasFontInformation.Manufacturer, CanvasFontInformation.Trademark, diff --git a/CharacterMap/CharacterMap/GlobalSuppressions.cs b/CharacterMap/CharacterMap/GlobalSuppressions.cs index 103f047e..de0e3d11 100644 --- a/CharacterMap/CharacterMap/GlobalSuppressions.cs +++ b/CharacterMap/CharacterMap/GlobalSuppressions.cs @@ -3,8 +3,25 @@ // Project-level suppressions either have no target or are given // a specific target and scoped to a namespace, type, member, etc. -using System.Diagnostics.CodeAnalysis; +global using CharacterMap.Core; +global using CharacterMap.Helpers; +global using CharacterMap.Models; +global using CharacterMap.Provider; +global using CharacterMap.Services; +global using CharacterMap.ViewModels; +global using CharacterMapCX; +global using CommunityToolkit.Mvvm.Messaging; +global using System; +global using System.Collections.Generic; +global using System.IO; +global using System.Linq; +global using System.Numerics; +global using System.Runtime.CompilerServices; +global using System.Text; +global using System.Threading; +global using System.Threading.Tasks; +using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Naming", "VSSpell001:Spell Check", Justification = "BritishEnglish", Scope = "member", Target = "~P:CharacterMap.Core.FontVariant.SupportsColourRendering")] [assembly: SuppressMessage("Naming", "VSSpell001:Spell Check", Justification = "Correct", Scope = "member", Target = "~P:CharacterMap.Core.FontVariant.Panose")] [assembly: SuppressMessage("Naming", "VSSpell001:Spell Check", Justification = "", Scope = "member", Target = "~P:CharacterMap.ViewModels.SettingsViewModel.Changelog")] diff --git a/CharacterMap/CharacterMap/Helpers/Unicode.cs b/CharacterMap/CharacterMap/Helpers/Unicode.cs index 9925f843..10db9f02 100644 --- a/CharacterMap/CharacterMap/Helpers/Unicode.cs +++ b/CharacterMap/CharacterMap/Helpers/Unicode.cs @@ -1,10 +1,4 @@ -using CharacterMap.Core; -using CharacterMap.Models; -using CharacterMap.ViewModels; -using System; -using System.Collections.Generic; -using System.Linq; -using Windows.Data.Text; +using Windows.Data.Text; namespace CharacterMap.Helpers { diff --git a/CharacterMap/CharacterMap/Helpers/UnicodeScriptTags.cs b/CharacterMap/CharacterMap/Helpers/UnicodeScriptTags.cs new file mode 100644 index 00000000..ed0159b2 --- /dev/null +++ b/CharacterMap/CharacterMap/Helpers/UnicodeScriptTags.cs @@ -0,0 +1,196 @@ +namespace CharacterMap.Helpers; + +internal class UnicodeScriptTags +{ + public static Dictionary Scripts { get; } = new() + { + { "adlm", "Adlam" }, + { "ahom", "Ahom" }, + { "hluw", "Anatolian Hieroglyphs" }, + { "arab", "Arabic" }, + { "armn", "Armenian" }, + { "avst", "Avestan" }, + { "bali", "Balinese" }, + { "bamu", "Bamum" }, + { "bass", "Bassa Vah" }, + { "batk", "Batak" }, + { "beng", "Bangla" }, + { "bng2", "Bangla v.2" }, + { "bhks", "Bhaiksuki" }, + { "bopo", "Bopomofo" }, + { "brah", "Brahmi" }, + { "brai", "Braille" }, + { "bugi", "Buginese" }, + { "buhd", "Buhid" }, + { "byzm", "Byzantine Music" }, + { "cans", "Canadian Syllabics" }, + { "cari", "Carian" }, + { "aghb", "Caucasian Albanian" }, + { "cakm", "Chakma" }, + { "cham", "Cham" }, + { "cher", "Cherokee" }, + { "chrs", "Chorasmian" }, + { "hani", "CJK Ideographic" }, + { "copt", "Coptic" }, + { "cprt", "Cypriot Syllabary" }, + { "cpmn", "Cypro-Minoan" }, + { "cyrl", "Cyrillic" }, + { "bg-cyrl", "Cyrillic (Bulgarian)" }, + { "mk-cyrl", "Cyrillic (Macedonian)" }, + { "sr-cyrl", "Cyrillic (Serbian)" }, + { "DFLT", "Default" }, + { "dsrt", "Deseret" }, + { "deva", "Devanagari" }, + { "dev2", "Devanagari v.2" }, + { "diak", "Dives Akuru" }, + { "dogr", "Dogra" }, + { "dupl", "Duployan" }, + { "egyp", "Egyptian Hieroglyphs" }, + { "elba", "Elbasan" }, + { "elym", "Elymaic" }, + { "ethi", "Ethiopic" }, + { "geor", "Georgian" }, + { "glag", "Glagolitic" }, + { "goth", "Gothic" }, + { "gran", "Grantha" }, + { "grek", "Greek" }, + { "gujr", "Gujarati" }, + { "gjr2", "Gujarati v.2" }, + { "gong", "Gunjala Gondi" }, + { "guru", "Gurmukhi" }, + { "gur2", "Gurmukhi v.2" }, + { "hang", "Hangul" }, + { "jamo", "Hangul Jamo" }, + { "rohg", "Hanifi Rohingya" }, + { "hano", "Hanunoo" }, + { "hatr", "Hatran" }, + { "hebr", "Hebrew" }, + { "kana", "Hiragana / Katakana" }, + { "armi", "Imperial Aramaic" }, + { "phli", "Inscriptional Pahlavi" }, + { "prti", "Inscriptional Parthian" }, + { "java", "Javanese" }, + { "kthi", "Kaithi" }, + { "knda", "Kannada" }, + { "knd2", "Kannada v.2" }, + //{ "kana", "Katakana" }, + { "kali", "Kayah Li" }, + { "khar", "Kharosthi" }, + { "kits", "Khitan Small Script" }, + { "khmr", "Khmer" }, + { "khoj", "Khojki" }, + { "sind", "Khudawadi" }, + { "lao ", "Lao" }, + { "latn", "Latin" }, + { "vi-latn", "Latin (Vietnamese)" }, + { "lepc", "Lepcha" }, + { "limb", "Limbu" }, + { "lina", "Linear A" }, + { "linb", "Linear B" }, + { "lisu", "Lisu (Fraser)" }, + { "lyci", "Lycian" }, + { "lydi", "Lydian" }, + { "mahj", "Mahajani" }, + { "maka", "Makasar" }, + { "mlym", "Malayalam" }, + { "mlm2", "Malayalam v.2" }, + { "mand", "Mandaic, Mandaean" }, + { "mani", "Manichaean" }, + { "marc", "Marchen" }, + { "gonm", "Masaram Gondi" }, + { "math", "Mathematical Alphanumeric Symbols" }, + { "medf", "Medefaidrin (Oberi Okaime, Oberi Ɔkaimɛ)" }, + { "mtei", "Meitei Mayek (Meithei, Meetei)" }, + { "mend", "Mende Kikakui" }, + { "merc", "Meroitic Cursive" }, + { "mero", "Meroitic Hieroglyphs" }, + { "plrd", "Miao" }, + { "modi", "Modi" }, + { "mong", "Mongolian" }, + { "mroo", "Mro" }, + { "mult", "Multani" }, + { "musc", "Musical Symbols" }, + { "mymr", "Myanmar" }, + { "mym2", "Myanmar v.2" }, + { "nbat", "Nabataean" }, + { "nand", "Nandinagari" }, + { "newa", "Newa" }, + { "talu", "New Tai Lue" }, + { "nko ", "N'Ko" }, + { "nshu", "Nüshu" }, + { "hmnp", "Nyiakeng Puachue Hmong" }, + { "orya", "Odia" }, + { "ory2", "Odia v.2" }, + { "ogam", "Ogham" }, + { "olck", "Ol Chiki" }, + { "ital", "Old Italic" }, + { "hung", "Old Hungarian" }, + { "narb", "Old North Arabian" }, + { "perm", "Old Permic" }, + { "xpeo", "Old Persian Cuneiform" }, + { "sogo", "Old Sogdian" }, + { "sarb", "Old South Arabian" }, + { "orkh", "Old Turkic, Orkhon Runic" }, + { "ougr", "Old Uyghur" }, + { "osge", "Osage" }, + { "osma", "Osmanya" }, + { "hmng", "Pahawh Hmong" }, + { "palm", "Palmyrene" }, + { "pauc", "Pau Cin Hau" }, + { "phag", "Phags-pa" }, + { "phnx", "Phoenician" }, + { "phlp", "Psalter Pahlavi" }, + { "rjng", "Rejang" }, + { "runr", "Runic" }, + { "samr", "Samaritan" }, + { "saur", "Saurashtra" }, + { "shrd", "Sharada" }, + { "shaw", "Shavian" }, + { "sidd", "Siddham" }, + { "sgnw", "Sign Writing" }, + { "sinh", "Sinhala" }, + { "sogd", "Sogdian" }, + { "sora", "Sora Sompeng" }, + { "soyo", "Soyombo" }, + { "xsux", "Sumero-Akkadian Cuneiform" }, + { "sund", "Sundanese" }, + { "sylo", "Syloti Nagri" }, + { "syrc", "Syriac" }, + { "tglg", "Tagalog" }, + { "tagb", "Tagbanwa" }, + { "tale", "Tai Le" }, + { "lana", "Tai Tham (Lanna)" }, + { "tavt", "Tai Viet" }, + { "takr", "Takri" }, + { "taml", "Tamil" }, + { "tml2", "Tamil v.2" }, + { "tnsa", "Tangsa" }, + { "tang", "Tangut" }, + { "telu", "Telugu" }, + { "tel2", "Telugu v.2" }, + { "thaa", "Thaana" }, + { "thai", "Thai" }, + { "tibt", "Tibetan" }, + { "tfng", "Tifinagh" }, + { "tirh", "Tirhuta" }, + { "toto", "Toto" }, + { "ugar", "Ugaritic Cuneiform" }, + { "vai ", "Vai" }, + { "vith", "Vithkuqi" }, + { "wcho", "Wancho" }, + { "wara", "Warang Citi" }, + { "yezi", "Yezidi" }, + { "yi ", "Yi" }, + { "zan", "Zanabazar Square" }, + { "zsye", "Emoji Style" }, + { "zsym", "Text Style emoji" } + }; + + public static string GetName(string tag) + { + if (Scripts.TryGetValue(tag.ToLower(), out string name)) + return name; + + return tag; + } +} diff --git a/CharacterMap/CharacterMap/Provider/SQLiteGlyphProvider.cs b/CharacterMap/CharacterMap/Provider/SQLiteGlyphProvider.cs index e0f23334..f095b5f3 100644 --- a/CharacterMap/CharacterMap/Provider/SQLiteGlyphProvider.cs +++ b/CharacterMap/CharacterMap/Provider/SQLiteGlyphProvider.cs @@ -1,16 +1,6 @@ -using CharacterMap.Core; -using CharacterMap.Services; -using SQLite; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using SQLite; using Windows.ApplicationModel; using Windows.UI.Xaml.Controls; -using CharacterMap.Models; -using System.Diagnostics; namespace CharacterMap.Provider { diff --git a/CharacterMap/CharacterMap/Services/GlyphService.cs b/CharacterMap/CharacterMap/Services/GlyphService.cs index aba82b46..613ffc7a 100644 --- a/CharacterMap/CharacterMap/Services/GlyphService.cs +++ b/CharacterMap/CharacterMap/Services/GlyphService.cs @@ -1,13 +1,6 @@ //#define GENERATE_DATA -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.Provider; using SQLite; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Windows.UI.Xaml; namespace CharacterMap.Services diff --git a/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs b/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs index b66b1538..4d94b036 100644 --- a/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs +++ b/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs @@ -1,19 +1,7 @@ using CharacterMap.Controls; -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.Services; -using CharacterMap.ViewModels; -using CharacterMapCX; using CommunityToolkit.Mvvm.DependencyInjection; -using CommunityToolkit.Mvvm.Messaging; using Microsoft.Toolkit.Uwp.UI.Controls; -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; using Windows.Storage; using Windows.System; using Windows.UI.Core; From 381b90d0db30ef3162f6c0f04bd15fedd5df4d37 Mon Sep 17 00:00:00 2001 From: Johnny Westlake Date: Tue, 24 Oct 2023 13:49:08 +0100 Subject: [PATCH 04/31] Support filtering by Design tag --- .../Controls/FilterFlyout.xaml.cs | 21 ++++++++++++++----- CharacterMap/CharacterMap/Core/FontVariant.cs | 12 +++++------ .../CharacterMap/Helpers/UnicodeScriptTags.cs | 2 +- .../CharacterMap/Models/BasicFontFilter.cs | 8 +++++++ .../CharacterMap/Strings/en-US/Resources.resw | 3 +++ 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs b/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs index 32fe4ae3..d6d601a8 100644 --- a/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs +++ b/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs @@ -3,6 +3,7 @@ using CharacterMap.Models; using CharacterMap.Services; using CommunityToolkit.Mvvm.DependencyInjection; +using Microsoft.UI.Xaml.Controls.Primitives; using System; using System.Collections.Generic; using System.Linq; @@ -60,8 +61,13 @@ private void Create() Add(BasicFontFilter.SansSerifFonts); Add(BasicFontFilter.SymbolFonts); - AddSub("OptionSupportedScripts/Text") - .Add(BasicFontFilter.ScriptArabic, style) + var sub = AddSub("OptionSupportedScripts/Text"); + var design = AddSub("OptionDesignTag/Text", sub); + foreach (var script in UnicodeScriptTags.Scripts) + design.Add(BasicFontFilter.ForDesignScriptTag(script.Value, script.Value), style); + + AddSep(sub); + sub.Add(BasicFontFilter.ScriptArabic, style) .Add(BasicFontFilter.ScriptBengali, style) .Add(BasicFontFilter.ScriptCyrillic, style) .Add(BasicFontFilter.ScriptDevanagari, style) @@ -73,6 +79,8 @@ private void Create() .Add(BasicFontFilter.ScriptCJKUnifiedIdeographs, style) .Add(BasicFontFilter.ScriptKoreanHangul, style); + + _ops = AddSub("OptionMoreFilters/Text") .Add(BasicFontFilter.ColorFonts, style) .Add(BasicFontFilter.PanoseDecorativeFonts, style) @@ -122,10 +130,13 @@ MenuFlyoutSubItem AddChild(MenuFlyoutSubItem menu, string key) return item; } - MenuFlyoutSubItem AddSub(string key) + MenuFlyoutSubItem AddSub(string key, MenuFlyoutSubItem parent = null) { - MenuFlyoutSubItem item = new() { Text = Localization.Get(key), Style = subStyle }; - this.Items.Add(item); + MenuFlyoutSubItem item = new() { Text = Localization.Get(key) ?? key, Style = subStyle }; + if (parent is not null) + parent.Items.Add(item); + else + this.Items.Add(item); return item; } diff --git a/CharacterMap/CharacterMap/Core/FontVariant.cs b/CharacterMap/CharacterMap/Core/FontVariant.cs index 9c59ee13..27e7a9b8 100644 --- a/CharacterMap/CharacterMap/Core/FontVariant.cs +++ b/CharacterMap/CharacterMap/Core/FontVariant.cs @@ -183,7 +183,7 @@ public IReadOnlyList GetCharacters() /// public string TryGetFullName() => TryGetInfo(CanvasFontInformation.FullName)?.Value ?? PreferredName; - + public bool HasDesignScriptTag(string tag) => TryGetInfo(CanvasFontInformation.DesignScriptLanguageTag)?.Values.Contains(tag) ?? false; /* SEARCHING */ @@ -205,11 +205,6 @@ public string GetDescription(Character c) /* INTERNAL */ - private List GetFontInformation() - => INFORMATIONS.Select(ReadInfoKey) - .Where(s => s != null && !string.IsNullOrWhiteSpace(s.Value)) - .ToList(); - private IReadOnlyList LoadTypographyFeatures(bool isXaml = false) { var features = TypographyAnalyzer.GetSupportedTypographyFeatures(this); @@ -226,6 +221,11 @@ private IReadOnlyList LoadTypographyFeatures(bool isXaml return isXaml ? _xamlTypographyFeatures : _typographyFeatures; } + private List GetFontInformation() + => INFORMATIONS.Select(ReadInfoKey) + .Where(s => s != null && !string.IsNullOrWhiteSpace(s.Value)) + .ToList(); + /// /// Reads an info key from the underlying DWriteFontFace /// diff --git a/CharacterMap/CharacterMap/Helpers/UnicodeScriptTags.cs b/CharacterMap/CharacterMap/Helpers/UnicodeScriptTags.cs index ed0159b2..4d9ded0b 100644 --- a/CharacterMap/CharacterMap/Helpers/UnicodeScriptTags.cs +++ b/CharacterMap/CharacterMap/Helpers/UnicodeScriptTags.cs @@ -99,7 +99,7 @@ internal class UnicodeScriptTags { "marc", "Marchen" }, { "gonm", "Masaram Gondi" }, { "math", "Mathematical Alphanumeric Symbols" }, - { "medf", "Medefaidrin (Oberi Okaime, Oberi Ɔkaimɛ)" }, + { "medf", "Medefaidrin" }, // (Oberi Okaime, Oberi Ɔkaimɛ)" }, { "mtei", "Meitei Mayek (Meithei, Meetei)" }, { "mend", "Mende Kikakui" }, { "merc", "Meroitic Cursive" }, diff --git a/CharacterMap/CharacterMap/Models/BasicFontFilter.cs b/CharacterMap/CharacterMap/Models/BasicFontFilter.cs index f3418226..aaf3458b 100644 --- a/CharacterMap/CharacterMap/Models/BasicFontFilter.cs +++ b/CharacterMap/CharacterMap/Models/BasicFontFilter.cs @@ -24,6 +24,14 @@ public partial class BasicFontFilter /// public bool RequiresAsync { get; set; } + public static BasicFontFilter ForDesignScriptTag(string tag, string displayTitle) + { + return new BasicFontFilter( + (f, c) => f.Where(i => i.Variants.Any(v => v.HasDesignScriptTag(tag))), + displayTitle, + true); + } + public static BasicFontFilter ForRange(UnicodeRange range, string displayTitle) { return new BasicFontFilter((f, c) => f.Where(v => v.Variants.Any(v => Unicode.ContainsRange(v, range))), displayTitle); diff --git a/CharacterMap/CharacterMap/Strings/en-US/Resources.resw b/CharacterMap/CharacterMap/Strings/en-US/Resources.resw index a6ab15e1..81a7de44 100644 --- a/CharacterMap/CharacterMap/Strings/en-US/Resources.resw +++ b/CharacterMap/CharacterMap/Strings/en-US/Resources.resw @@ -1444,4 +1444,7 @@ Tap and hold to arrange. Export Installed Fonts + + Design Tag + \ No newline at end of file From c657b808d5ce0829cb56cd5a70a4dda0f202deac Mon Sep 17 00:00:00 2001 From: Johnny Westlake Date: Tue, 24 Oct 2023 14:15:12 +0100 Subject: [PATCH 05/31] Use `global using` --- CharacterMap/CharacterMap/App.xaml.cs | 139 +- .../Controls/FilterFlyout.xaml.cs | 11 +- .../CharacterMap/Converters/Converters.cs | 140 +- .../CharacterMap/Core/AlphaKeyGroup.cs | 98 +- CharacterMap/CharacterMap/Core/AppSettings.cs | 14 +- CharacterMap/CharacterMap/Core/Converters.cs | 13 +- .../CharacterMap/Core/ExportManager.Font.cs | 13 +- .../CharacterMap/Core/ExportManager.cs | 19 +- CharacterMap/CharacterMap/Core/FontFinder.cs | 968 ++++---- CharacterMap/CharacterMap/Core/FontVariant.cs | 462 ++-- CharacterMap/CharacterMap/Core/Index.cs | 383 ++-- .../CharacterMap/Core/InstalledFont.cs | 172 +- CharacterMap/CharacterMap/Core/Localizer.cs | 48 +- .../CharacterMap/Core/PanoseParser.cs | 11 +- CharacterMap/CharacterMap/Core/Properties.cs | 1997 ++++++++--------- .../CharacterMap/Core/SVGPathReciever.cs | 6 - .../CharacterMap/Core/TypographyAnalyzer.cs | 128 +- .../CharacterMap/Core/TypographyBehavior.cs | 369 ++- .../Core/TypographyFeatureInfo.cs | 127 +- CharacterMap/CharacterMap/Core/Utils.cs | 819 ++++--- .../CharacterMap/GlobalSuppressions.cs | 6 + .../CharacterMap/Helpers/Animation.cs | 5 +- .../CharacterMap/Helpers/ConcurrencyToken.cs | 55 +- .../CharacterMap/Helpers/Debouncer.cs | 64 +- .../CharacterMap/Helpers/Extensions.cs | 269 ++- .../CharacterMap/Helpers/FluentAnimation.cs | 4 +- .../CharacterMap/Helpers/FlyoutHelper.cs | 972 ++++---- .../CharacterMap/Helpers/FontConverter.cs | 220 +- .../Helpers/InAppNotificationHelper.cs | 121 +- .../CharacterMap/Helpers/Localization.cs | 56 +- .../CharacterMap/Helpers/NavigationHelper.cs | 130 +- .../CharacterMap/Helpers/PrintHelper.cs | 549 +++-- .../CharacterMap/Helpers/ResourceHelper.cs | 506 ++--- .../CharacterMap/Helpers/Singleton.cs | 18 +- .../CharacterMap/Helpers/SystemInformation.cs | 316 ++- .../CharacterMap/Helpers/Throttler.cs | 21 +- CharacterMap/CharacterMap/Helpers/Unicode.cs | 141 +- .../CharacterMap/Helpers/WoffToOtf.cs | 608 +++-- .../Models/AddToCollectionResult.cs | 62 +- .../CharacterMap/Models/BasicFontFilter.cs | 259 +-- .../CharacterMap/Models/ChangelogItem.cs | 7 +- CharacterMap/CharacterMap/Models/Character.cs | 113 +- .../Models/CharacterRenderingOptions.cs | 8 +- CharacterMap/CharacterMap/Models/DevOption.cs | 37 +- .../CharacterMap/Models/ExportNamingScheme.cs | 27 +- .../CharacterMap/Models/ExportParameters.cs | 41 +- .../CharacterMap/Models/FolderContents.cs | 151 +- CharacterMap/CharacterMap/Models/FontItem.cs | 132 +- .../CharacterMap/Models/GlyphAnnotation.cs | 13 +- CharacterMap/CharacterMap/Models/Messages.cs | 225 +- .../CharacterMap/Models/PanoseFamily.cs | 921 ++++---- .../CharacterMap/Models/PrintLayout.cs | 13 +- .../CharacterMap/Models/SearchTarget.cs | 113 +- .../CharacterMap/Models/SupportedLanguage.cs | 50 +- .../Models/UnicodeCategoryModel.cs | 34 +- .../CharacterMap/Models/UnicodeGlyphData.cs | 35 +- .../CharacterMap/Models/UnicodeRange.cs | 747 +++--- .../CharacterMap/Models/UnicodeRangeModel.cs | 22 +- .../CharacterMap/Views/MainPage.xaml.cs | 12 - CharacterMap/CharacterMap/Views/ViewBase.cs | 17 +- 60 files changed, 6334 insertions(+), 6703 deletions(-) diff --git a/CharacterMap/CharacterMap/App.xaml.cs b/CharacterMap/CharacterMap/App.xaml.cs index 150d3b8f..6ce95093 100644 --- a/CharacterMap/CharacterMap/App.xaml.cs +++ b/CharacterMap/CharacterMap/App.xaml.cs @@ -1,101 +1,94 @@ -using System; using Windows.ApplicationModel.Activation; using Windows.UI.Xaml; -using CharacterMap.Core; -using CharacterMap.Services; using CharacterMap.Controls; using UnhandledExceptionEventArgs = CharacterMap.Core.UnhandledExceptionEventArgs; using CharacterMapCX.Controls; -using CharacterMap.ViewModels; using Windows.ApplicationModel.Core; -using CommunityToolkit.Mvvm.DependencyInjection; -using CharacterMap.Helpers; using SQLite; -namespace CharacterMap +namespace CharacterMap; + +sealed partial class App : Application { - sealed partial class App : Application - { - private Lazy _activationService { get; } - internal ActivationService ActivationService => _activationService.Value; + private Lazy _activationService { get; } + internal ActivationService ActivationService => _activationService.Value; - public new static App Current { get; private set; } + public new static App Current { get; private set; } - public App() - { - //Set app language - if (ResourceHelper.AppSettings.GetUserLanguageID() is string language) - Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = language; + public App() + { + //Set app language + if (ResourceHelper.AppSettings.GetUserLanguageID() is string language) + Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = language; - CoreApplication.EnablePrelaunch(true); + CoreApplication.EnablePrelaunch(true); - this.FocusVisualKind = FocusVisualKind.Reveal; - var loc = new ViewModelLocator(); - this.InitializeComponent(); + this.FocusVisualKind = FocusVisualKind.Reveal; + var loc = new ViewModelLocator(); + this.InitializeComponent(); - this.UnhandledException += OnUnhandledException; - this.Suspending += App_Suspending; - _activationService = new Lazy(CreateActivationService); - Current = this; + this.UnhandledException += OnUnhandledException; + this.Suspending += App_Suspending; + _activationService = new Lazy(CreateActivationService); + Current = this; - DirectText.RegisterDependencyProperties(); - SQLitePCL.raw.SetProvider(SQLiteConnection.Provider); - } + DirectText.RegisterDependencyProperties(); + SQLitePCL.raw.SetProvider(SQLiteConnection.Provider); + } - private async void App_Suspending(object sender, Windows.ApplicationModel.SuspendingEventArgs e) - { - var d = e.SuspendingOperation.GetDeferral(); - await Ioc.Default.GetService().FlushAsync(); - d.Complete(); - } + private async void App_Suspending(object sender, Windows.ApplicationModel.SuspendingEventArgs e) + { + var d = e.SuspendingOperation.GetDeferral(); + await Ioc.Default.GetService().FlushAsync(); + d.Complete(); + } - private void RegisterExceptionHandlingSynchronizationContext() - { - ExceptionHandlingSynchronizationContext - .Register() - .UnhandledException += SynchronizationContext_UnhandledException; - } + private void RegisterExceptionHandlingSynchronizationContext() + { + ExceptionHandlingSynchronizationContext + .Register() + .UnhandledException += SynchronizationContext_UnhandledException; + } - private void SynchronizationContext_UnhandledException(object sender, UnhandledExceptionEventArgs e) - { - e.Handled = true; - UnhandledExceptionDialog.Show(e.Exception); - } + private void SynchronizationContext_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + e.Handled = true; + UnhandledExceptionDialog.Show(e.Exception); + } - private void OnUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e) - { - e.Handled = true; - UnhandledExceptionDialog.Show(e.Exception); - } + private void OnUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e) + { + e.Handled = true; + UnhandledExceptionDialog.Show(e.Exception); + } - protected override async void OnLaunched(LaunchActivatedEventArgs e) + protected override async void OnLaunched(LaunchActivatedEventArgs e) + { + if (!e.PrelaunchActivated) { - if (!e.PrelaunchActivated) - { - await ActivationService.ActivateAsync(e); - } - else - { - await FontFinder.LoadFontsAsync(); - await Ioc.Default.GetService().LoadCollectionsAsync(); - } + await ActivationService.ActivateAsync(e); } - - protected override async void OnActivated(IActivatedEventArgs args) + else { - RegisterExceptionHandlingSynchronizationContext(); - await ActivationService.ActivateAsync(args); + await FontFinder.LoadFontsAsync(); + await Ioc.Default.GetService().LoadCollectionsAsync(); } + } - protected override async void OnFileActivated(FileActivatedEventArgs args) - { - base.OnFileActivated(args); - await ActivationService.ActivateAsync(args); - } + protected override async void OnActivated(IActivatedEventArgs args) + { + RegisterExceptionHandlingSynchronizationContext(); + await ActivationService.ActivateAsync(args); + } - private ActivationService CreateActivationService() - { - return new ActivationService(this, typeof(ViewModels.MainViewModel)); - } + protected override async void OnFileActivated(FileActivatedEventArgs args) + { + base.OnFileActivated(args); + await ActivationService.ActivateAsync(args); + } + + private ActivationService CreateActivationService() + { + return new ActivationService(this, typeof(ViewModels.MainViewModel)); } } diff --git a/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs b/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs index d6d601a8..1ddad209 100644 --- a/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs +++ b/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs @@ -1,13 +1,4 @@ -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.Services; -using CommunityToolkit.Mvvm.DependencyInjection; -using Microsoft.UI.Xaml.Controls.Primitives; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows.Input; +using System.Windows.Input; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; diff --git a/CharacterMap/CharacterMap/Converters/Converters.cs b/CharacterMap/CharacterMap/Converters/Converters.cs index e8e2553c..9d88889d 100644 --- a/CharacterMap/CharacterMap/Converters/Converters.cs +++ b/CharacterMap/CharacterMap/Converters/Converters.cs @@ -1,107 +1,101 @@ -using System; -using Windows.UI; -using Windows.UI.Xaml.Data; -using Windows.UI.Xaml.Media; -using CharacterMap.Core; -using CharacterMap.Helpers; +using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Controls; -namespace CharacterMap.Converters +namespace CharacterMap.Converters; + +public class PassthroughConverter : IValueConverter { - public class PassthroughConverter : IValueConverter + public object Convert(object value, Type targetType, object parameter, string language) { - public object Convert(object value, Type targetType, object parameter, string language) - { - if (parameter is string p) - return Localization.Get(p); + if (parameter is string p) + return Localization.Get(p); - return value; - } + return value; + } - public object ConvertBack(object value, Type targetType, object parameter, string language) - { - throw new NotImplementedException(); - } + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); } +} - public class CasingConverter : IValueConverter +public class CasingConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, string language) { - public object Convert(object value, Type targetType, object parameter, string language) + if (parameter is CharacterCasing casing && value is string s) { - if (parameter is CharacterCasing casing && value is string s) + return casing switch { - return casing switch - { - CharacterCasing.Upper => s.ToUpper(), - CharacterCasing.Lower => s.ToLower(), - _ => s - }; - } - - return value; + CharacterCasing.Upper => s.ToUpper(), + CharacterCasing.Lower => s.ToLower(), + _ => s + }; } - public object ConvertBack(object value, Type targetType, object parameter, string language) - { - throw new NotImplementedException(); - } + return value; } + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } +} + - public class LowerConverter : IValueConverter +public class LowerConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, string language) { - public object Convert(object value, Type targetType, object parameter, string language) - { - if (parameter is string p) - return Localization.Get(p).ToLower(); + if (parameter is string p) + return Localization.Get(p).ToLower(); - if (value is string s) - return s.ToLower(); + if (value is string s) + return s.ToLower(); - return value; - } + return value; + } - public object ConvertBack(object value, Type targetType, object parameter, string language) - { - throw new NotImplementedException(); - } + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); } +} - public class UpperConverter : IValueConverter +public class UpperConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, string language) { - public object Convert(object value, Type targetType, object parameter, string language) - { - if (parameter is string p) - value = Localization.Get(p).ToUpper(); + if (parameter is string p) + value = Localization.Get(p).ToUpper(); - if (value is string s) - return s.ToUpper(); + if (value is string s) + return s.ToUpper(); - return null; - } + return null; + } - public object ConvertBack(object value, Type targetType, object parameter, string language) - { - throw new NotImplementedException(); - } + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); } +} - public class ZoomBackgroundConverter : IValueConverter +public class ZoomBackgroundConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, string language) { - public object Convert(object value, Type targetType, object parameter, string language) + if (value != null) { - if (value != null) - { - var count = int.Parse(value.ToString()); - if (count > 0) - return 1; - } - return 0.3; + var count = int.Parse(value.ToString()); + if (count > 0) + return 1; } + return 0.3; + } - public object ConvertBack(object value, Type targetType, object parameter, string language) - { - throw new NotImplementedException(); - } + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); } } diff --git a/CharacterMap/CharacterMap/Core/AlphaKeyGroup.cs b/CharacterMap/CharacterMap/Core/AlphaKeyGroup.cs index f3140dca..1a90edb6 100644 --- a/CharacterMap/CharacterMap/Core/AlphaKeyGroup.cs +++ b/CharacterMap/CharacterMap/Core/AlphaKeyGroup.cs @@ -1,68 +1,62 @@ -using CharacterMap.Models; -using System; -using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Linq; using Windows.Globalization.Collation; -namespace CharacterMap.Core -{ - public class UnicodeRangeGroup : List, IGrouping - { - public NamedUnicodeRange Key { get; private set; } +namespace CharacterMap.Core; - public UnicodeRangeGroup(NamedUnicodeRange key, IEnumerable items) : base(items) - { - Key = key; - } +public class UnicodeRangeGroup : List, IGrouping +{ + public NamedUnicodeRange Key { get; private set; } - public static ObservableCollection CreateGroups(IEnumerable items) - { - return new(items - .GroupBy(i => i.Range ?? throw new InvalidOperationException(), c => c) - .Select(g=> new UnicodeRangeGroup(g.Key, g))); - } + public UnicodeRangeGroup(NamedUnicodeRange key, IEnumerable items) : base(items) + { + Key = key; + } - public override string ToString() - { - return Key.Name; - } + public static ObservableCollection CreateGroups(IEnumerable items) + { + return new(items + .GroupBy(i => i.Range ?? throw new InvalidOperationException(), c => c) + .Select(g=> new UnicodeRangeGroup(g.Key, g))); } - public class AlphaKeyGroup : ObservableCollection + public override string ToString() { - public string Key { get; private set; } + return Key.Name; + } +} - public AlphaKeyGroup(string key) - { - Key = key; - } +public class AlphaKeyGroup : ObservableCollection +{ + public string Key { get; private set; } - // Work around for Chinese version of Windows - // By default, Chinese language group will create useless "拼音A-Z" groups. - private static List> CreateAZGroups() - { - char[] alpha = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ&".ToCharArray(); - var list = alpha.Select(c => new AlphaKeyGroup(c.ToString())).ToList(); - return list; - } + public AlphaKeyGroup(string key) + { + Key = key; + } + + // Work around for Chinese version of Windows + // By default, Chinese language group will create useless "拼音A-Z" groups. + private static List> CreateAZGroups() + { + char[] alpha = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ&".ToCharArray(); + var list = alpha.Select(c => new AlphaKeyGroup(c.ToString())).ToList(); + return list; + } - public static List> CreateGroups(IEnumerable items, Func keySelector) + public static List> CreateGroups(IEnumerable items, Func keySelector) + { + CharacterGroupings slg = new (); + List> list = CreateAZGroups(); //CreateDefaultGroups(slg); + foreach (T item in items) { - CharacterGroupings slg = new (); - List> list = CreateAZGroups(); //CreateDefaultGroups(slg); - foreach (T item in items) - { - int index = 0; - string label = slg.Lookup(keySelector(item)); - index = list.FindIndex(alphagroupkey => (alphagroupkey.Key.Equals(label, StringComparison.CurrentCulture))); - if (index > -1 && index < list.Count) - list[index].Add(item); - else - list.Last().Add(item); - } - return list; + int index = 0; + string label = slg.Lookup(keySelector(item)); + index = list.FindIndex(alphagroupkey => (alphagroupkey.Key.Equals(label, StringComparison.CurrentCulture))); + if (index > -1 && index < list.Count) + list[index].Add(item); + else + list.Last().Add(item); } + return list; } } diff --git a/CharacterMap/CharacterMap/Core/AppSettings.cs b/CharacterMap/CharacterMap/Core/AppSettings.cs index abdf9463..5c191aa3 100644 --- a/CharacterMap/CharacterMap/Core/AppSettings.cs +++ b/CharacterMap/CharacterMap/Core/AppSettings.cs @@ -1,17 +1,5 @@ -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.Provider; -using CharacterMap.Services; -using CharacterMap.ViewModels; -using CommunityToolkit.Mvvm.Messaging; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; +using System.ComponentModel; using Windows.Foundation.Metadata; -using Windows.Storage; using Windows.UI.Xaml; namespace CharacterMap.Core diff --git a/CharacterMap/CharacterMap/Core/Converters.cs b/CharacterMap/CharacterMap/Core/Converters.cs index 863dd3b7..5055d80e 100644 --- a/CharacterMap/CharacterMap/Core/Converters.cs +++ b/CharacterMap/CharacterMap/Core/Converters.cs @@ -1,16 +1,5 @@ -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.Services; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Windows.UI; -using Windows.UI.Xaml; -using CommunityToolkit.Mvvm.DependencyInjection; +using Windows.UI.Xaml; using Windows.UI.Xaml.Media; -using CharacterMapCX; namespace CharacterMap.Core { diff --git a/CharacterMap/CharacterMap/Core/ExportManager.Font.cs b/CharacterMap/CharacterMap/Core/ExportManager.Font.cs index 0ec70275..42bfa630 100644 --- a/CharacterMap/CharacterMap/Core/ExportManager.Font.cs +++ b/CharacterMap/CharacterMap/Core/ExportManager.Font.cs @@ -1,15 +1,4 @@ -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMapCX; -using CommunityToolkit.Mvvm.Messaging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Windows.Storage.Streams; -using Windows.Storage; -using System.IO.Compression; -using System.IO; +using System.IO.Compression; using Windows.Storage.Pickers; namespace CharacterMap.Core diff --git a/CharacterMap/CharacterMap/Core/ExportManager.cs b/CharacterMap/CharacterMap/Core/ExportManager.cs index b61bfb3c..a9fb5809 100644 --- a/CharacterMap/CharacterMap/Core/ExportManager.cs +++ b/CharacterMap/CharacterMap/Core/ExportManager.cs @@ -1,28 +1,13 @@ -using CharacterMap.Helpers; -using CharacterMap.Services; -using CharacterMapCX; -using Microsoft.Graphics.Canvas; +using Microsoft.Graphics.Canvas; using Microsoft.Graphics.Canvas.Geometry; using Microsoft.Graphics.Canvas.Svg; using Microsoft.Graphics.Canvas.Text; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; +using System.IO.Compression; using Windows.Foundation; -using Windows.Storage; using Windows.Storage.Pickers; -using Windows.Storage.Streams; using Windows.UI; using Windows.UI.Xaml.Markup; using Windows.UI.Xaml.Media; -using CharacterMap.Models; -using System.IO; -using System.IO.Compression; -using System.Runtime.InteropServices.WindowsRuntime; -using CommunityToolkit.Mvvm.DependencyInjection; -using System.Threading; namespace CharacterMap.Core { diff --git a/CharacterMap/CharacterMap/Core/FontFinder.cs b/CharacterMap/CharacterMap/Core/FontFinder.cs index 92f05cc3..5ac0b1bc 100644 --- a/CharacterMap/CharacterMap/Core/FontFinder.cs +++ b/CharacterMap/CharacterMap/Core/FontFinder.cs @@ -1,641 +1,639 @@ using System.Diagnostics; -using Windows.Storage; using Windows.Storage.Search; using Windows.UI.Text; using WoffToOtf; -namespace CharacterMap.Core +namespace CharacterMap.Core; + +public class FontImportResult { - public class FontImportResult + public FontImportResult(List imported, List existing, List<(IStorageItem, string)> invalid) { - public FontImportResult(List imported, List existing, List<(IStorageItem, string)> invalid) - { - Imported = imported; - Existing = existing; - Invalid = invalid; - } - - public List Imported { get; } - public List Existing { get; } - public List<(IStorageItem, string)> Invalid { get; } + Imported = imported; + Existing = existing; + Invalid = invalid; } - public class FontFinder - { - private const string PENDING = nameof(PENDING); - private const string TEMP = nameof(TEMP); + public List Imported { get; } + public List Existing { get; } + public List<(IStorageItem, string)> Invalid { get; } +} - private static SemaphoreSlim _initSemaphore { get; } = new (1,1); - private static SemaphoreSlim _loadSemaphore { get; } = new (1,1); +public class FontFinder +{ + private const string PENDING = nameof(PENDING); + private const string TEMP = nameof(TEMP); - /* If we can't delete a font during a session, we mark it here */ - private static HashSet _ignoredFonts { get; } = new (); + private static SemaphoreSlim _initSemaphore { get; } = new (1,1); + private static SemaphoreSlim _loadSemaphore { get; } = new (1,1); - private static StorageFolder _importFolder => ApplicationData.Current.LocalFolder; + /* If we can't delete a font during a session, we mark it here */ + private static HashSet _ignoredFonts { get; } = new (); - public static Dictionary FontDictionary { get; private set; } - public static IReadOnlyList Fonts { get; private set; } - public static IReadOnlyList ImportedFonts { get; private set; } + private static StorageFolder _importFolder => ApplicationData.Current.LocalFolder; - public static InstalledFont DefaultFont { get; private set; } - public static bool HasAppxFonts { get; private set; } - public static bool HasRemoteFonts { get; private set; } - public static bool HasVariableFonts { get; private set; } + public static Dictionary FontDictionary { get; private set; } + public static IReadOnlyList Fonts { get; private set; } + public static IReadOnlyList ImportedFonts { get; private set; } - public static DWriteFallbackFont Fallback { get; private set; } + public static InstalledFont DefaultFont { get; private set; } + public static bool HasAppxFonts { get; private set; } + public static bool HasRemoteFonts { get; private set; } + public static bool HasVariableFonts { get; private set; } - public static HashSet SupportedFormats { get; } = new () - { - ".ttf", ".otf", ".otc", ".ttc", // ".woff", ".woff2" - }; + public static DWriteFallbackFont Fallback { get; private set; } - public static HashSet ImportFormats { get; } = new () - { - ".ttf", ".otf", ".otc", ".ttc", ".woff", ".zip", ".woff2" - }; + public static HashSet SupportedFormats { get; } = new () + { + ".ttf", ".otf", ".otc", ".ttc", // ".woff", ".woff2" + }; + public static HashSet ImportFormats { get; } = new () + { + ".ttf", ".otf", ".otc", ".ttc", ".woff", ".zip", ".woff2" + }; - public static async Task InitialiseAsync() - { - await _initSemaphore.WaitAsync().ConfigureAwait(false); - - NativeInterop interop = Utils.GetInterop(); - DWriteFontSet systemFonts = interop.GetSystemFonts(); - Parallel.ForEach(systemFonts.Families, new ParallelOptions { MaxDegreeOfParallelism = 50 }, l => - { - l.Inflate(); - }); - systemFonts.Update(); + public static async Task InitialiseAsync() + { + await _initSemaphore.WaitAsync().ConfigureAwait(false); + + NativeInterop interop = Utils.GetInterop(); + DWriteFontSet systemFonts = interop.GetSystemFonts(); - try - { - if (DefaultFont == null) - { - DWriteFontFace segoe = systemFonts.Fonts.FirstOrDefault( - f => f.Properties.FamilyName == "Segoe UI" - && f.Properties.Weight.Weight == FontWeights.Normal.Weight - && f.Properties.Stretch == FontStretch.Normal - && f.Properties.Style == FontStyle.Normal); - - if (segoe != null) - DefaultFont = InstalledFont.CreateDefault(segoe); - } - } - finally + Parallel.ForEach(systemFonts.Families, new ParallelOptions { MaxDegreeOfParallelism = 50 }, l => + { + l.Inflate(); + }); + systemFonts.Update(); + + try + { + if (DefaultFont == null) { - _initSemaphore.Release(); + DWriteFontFace segoe = systemFonts.Fonts.FirstOrDefault( + f => f.Properties.FamilyName == "Segoe UI" + && f.Properties.Weight.Weight == FontWeights.Normal.Weight + && f.Properties.Stretch == FontStretch.Normal + && f.Properties.Style == FontStyle.Normal); + + if (segoe != null) + DefaultFont = InstalledFont.CreateDefault(segoe); } - - return systemFonts; + } + finally + { + _initSemaphore.Release(); } - public static int SystemFamilyCount { get; private set; } - public static int SystemFaceCount { get; private set; } + return systemFonts; + } - public static Task LoadFontsAsync(bool clearExisting = true) - { - // It's possible to go down this path if the font collection - // was loaded during app pre-launch - if (clearExisting is false && Fonts is not null) - return Task.CompletedTask; + public static int SystemFamilyCount { get; private set; } + public static int SystemFaceCount { get; private set; } - Fonts = null; + public static Task LoadFontsAsync(bool clearExisting = true) + { + // It's possible to go down this path if the font collection + // was loaded during app pre-launch + if (clearExisting is false && Fonts is not null) + return Task.CompletedTask; - return Task.Run(async () => - { - // Reset meta to false; - UpdateMeta(null); + Fonts = null; - await _loadSemaphore.WaitAsync().ConfigureAwait(false); - NativeInterop interop = Utils.GetInterop(); + return Task.Run(async () => + { + // Reset meta to false; + UpdateMeta(null); + + await _loadSemaphore.WaitAsync().ConfigureAwait(false); + NativeInterop interop = Utils.GetInterop(); #if DEBUG - Stopwatch s = new Stopwatch(); - s.Start(); + Stopwatch s = new Stopwatch(); + s.Start(); #endif - // Load SystemFonts and imported fonts in parallel - // 1.1. Load imported fonts - IReadOnlyList files = null; - Task> setsTask = Task.Run(async () => - { - files = await _importFolder.GetFilesAsync(); - var sets = interop.GetFonts(files).ToList(); - return sets; - }); + // Load SystemFonts and imported fonts in parallel + // 1.1. Load imported fonts + IReadOnlyList files = null; + Task> setsTask = Task.Run(async () => + { + files = await _importFolder.GetFilesAsync(); + var sets = interop.GetFonts(files).ToList(); + return sets; + }); - // 1.2. Load installed fonts - Task init = InitialiseAsync(); + // 1.2. Load installed fonts + Task init = InitialiseAsync(); - // 1.3. Perform cleanup - Task delete = DefaultFont == null ? Task.WhenAll( - CleanUpTempFolderAsync(), - CleanUpPendingDeletesAsync()) : Task.CompletedTask; + // 1.3. Perform cleanup + Task delete = DefaultFont == null ? Task.WhenAll( + CleanUpTempFolderAsync(), + CleanUpPendingDeletesAsync()) : Task.CompletedTask; - await Task.WhenAll(init, delete, setsTask); + await Task.WhenAll(init, delete, setsTask); - // Load in System Fonts - DWriteFontSet systemFonts = init.Result; - Dictionary resultList = new (systemFonts.Fonts.Count); - UpdateMeta(systemFonts); + // Load in System Fonts + DWriteFontSet systemFonts = init.Result; + Dictionary resultList = new (systemFonts.Fonts.Count); + UpdateMeta(systemFonts); - /* Add imported fonts */ - IReadOnlyList sets = setsTask.Result; - for (int i = 0; i < files.Count; i++) - { - var file = files[i]; - if (_ignoredFonts.Contains(file.Name)) - continue; + /* Add imported fonts */ + IReadOnlyList sets = setsTask.Result; + for (int i = 0; i < files.Count; i++) + { + var file = files[i]; + if (_ignoredFonts.Contains(file.Name)) + continue; - DWriteFontSet importedFonts = sets[i]; - UpdateMeta(importedFonts); - foreach (DWriteFontFace font in importedFonts.Fonts) - { - AddFont(resultList, font, file); - } + DWriteFontSet importedFonts = sets[i]; + UpdateMeta(importedFonts); + foreach (DWriteFontFace font in importedFonts.Fonts) + { + AddFont(resultList, font, file); } + } - var imports = resultList.ToDictionary(d => d.Key, v => v.Value.Clone()); + var imports = resultList.ToDictionary(d => d.Key, v => v.Value.Clone()); - /* Add all system fonts */ - SystemFamilyCount = systemFonts.Families.Count; - SystemFaceCount = systemFonts.Fonts.Count; + /* Add all system fonts */ + SystemFamilyCount = systemFonts.Families.Count; + SystemFaceCount = systemFonts.Fonts.Count; - foreach (var font in systemFonts.Fonts) - AddFont(resultList, font); - - /* Order everything appropriately */ - Fonts = CreateFontList(resultList); - ImportedFonts = CreateFontList(imports); - FontDictionary = resultList; + foreach (var font in systemFonts.Fonts) + AddFont(resultList, font); + + /* Order everything appropriately */ + Fonts = CreateFontList(resultList); + ImportedFonts = CreateFontList(imports); + FontDictionary = resultList; - if (Fallback == null) - Fallback = interop.CreateEmptyFallback(); + if (Fallback == null) + Fallback = interop.CreateEmptyFallback(); #if DEBUG - s.Stop(); - var elasped = s.Elapsed; + s.Stop(); + var elasped = s.Elapsed; #endif - _loadSemaphore.Release(); + _loadSemaphore.Release(); - WeakReferenceMessenger.Default.Send(new FontListCreatedMessage()); - }); - } + WeakReferenceMessenger.Default.Send(new FontListCreatedMessage()); + }); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static List CreateFontList(Dictionary fonts) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static List CreateFontList(Dictionary fonts) + { + return fonts.OrderBy(f => f.Key).Select(f => { - return fonts.OrderBy(f => f.Key).Select(f => - { - f.Value.SortVariants(); - return f.Value; - }).ToList(); - } + f.Value.SortVariants(); + return f.Value; + }).ToList(); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void UpdateMeta(DWriteFontSet set) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void UpdateMeta(DWriteFontSet set) + { + if (set == null) { - if (set == null) - { - HasRemoteFonts = HasAppxFonts = HasVariableFonts = false; - return; - } - - HasRemoteFonts = HasRemoteFonts || set.CloudFontCount > 0; - HasAppxFonts = HasAppxFonts || set.AppxFontCount > 0; - HasVariableFonts = true; + HasRemoteFonts = HasAppxFonts = HasVariableFonts = false; + return; } - internal static List GetImportedVariants() - { - return Fonts.Where(f => f.HasImportedFiles) - .SelectMany(f => f.Variants.Where(v => v.IsImported)) - .ToList(); - } + HasRemoteFonts = HasRemoteFonts || set.CloudFontCount > 0; + HasAppxFonts = HasAppxFonts || set.AppxFontCount > 0; + HasVariableFonts = true; + } - internal static List GetSystemVariants() - { - return Fonts.SelectMany(f => f.Variants.Where(v => v.IsImported is false)) - .ToList(); - } + internal static List GetImportedVariants() + { + return Fonts.Where(f => f.HasImportedFiles) + .SelectMany(f => f.Variants.Where(v => v.IsImported)) + .ToList(); + } + + internal static List GetSystemVariants() + { + return Fonts.SelectMany(f => f.Variants.Where(v => v.IsImported is false)) + .ToList(); + } - /* - * Helper method for adding fonts. - */ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void AddFont( - IDictionary fontList, - DWriteFontFace font, - StorageFile file = null) + /* + * Helper method for adding fonts. + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AddFont( + IDictionary fontList, + DWriteFontFace font, + StorageFile file = null) + { + try { - try + var familyName = font.Properties.FamilyName; + if (!string.IsNullOrEmpty(familyName)) { - var familyName = font.Properties.FamilyName; - if (!string.IsNullOrEmpty(familyName)) + /* Check if we already have a listing for this fontFamily */ + if (fontList.TryGetValue(familyName, out var fontFamily)) { - /* Check if we already have a listing for this fontFamily */ - if (fontList.TryGetValue(familyName, out var fontFamily)) - { - fontFamily.AddVariant(font, file); - } - else - { - fontList[familyName] = new InstalledFont(familyName, font, file); - } + fontFamily.AddVariant(font, file); + } + else + { + fontList[familyName] = new InstalledFont(familyName, font, file); } } - catch (Exception) - { - // Corrupted font files throw an exception - } } + catch (Exception) + { + // Corrupted font files throw an exception + } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static string GetAppPath(StorageFile file) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static string GetAppPath(StorageFile file) + { + if (file.Path.StartsWith(ApplicationData.Current.TemporaryFolder.Path)) { - if (file.Path.StartsWith(ApplicationData.Current.TemporaryFolder.Path)) - { - var str = file.Path.Replace(ApplicationData.Current.TemporaryFolder.Path, "ms-appdata:///temp") - .Replace("\\", "/"); - return str; - } - var temp = Path.GetDirectoryName(file.Path).EndsWith(TEMP); - return $"ms-appdata:///local/{(temp ? $"{TEMP}/" : string.Empty)}{file.Name}"; + var str = file.Path.Replace(ApplicationData.Current.TemporaryFolder.Path, "ms-appdata:///temp") + .Replace("\\", "/"); + return str; } + var temp = Path.GetDirectoryName(file.Path).EndsWith(TEMP); + return $"ms-appdata:///local/{(temp ? $"{TEMP}/" : string.Empty)}{file.Name}"; + } - internal static Task ImportFontsAsync(IReadOnlyList items) + internal static Task ImportFontsAsync(IReadOnlyList items) + { + return Task.Run(async () => { - return Task.Run(async () => + List imported = new (); + List existing = new (); + List<(IStorageItem, string)> invalid = new (); + + foreach (var item in items) { - List imported = new (); - List existing = new (); - List<(IStorageItem, string)> invalid = new (); + if (item is not StorageFile file) + { + invalid.Add((item, Localization.Get("ImportNotAFile"))); + continue; + } - foreach (var item in items) + if (!file.IsAvailable) { - if (item is not StorageFile file) - { - invalid.Add((item, Localization.Get("ImportNotAFile"))); - continue; - } + invalid.Add((item, Localization.Get("ImportUnavailableFile"))); + continue; + } - if (!file.IsAvailable) - { - invalid.Add((item, Localization.Get("ImportUnavailableFile"))); - continue; - } + if (_ignoredFonts.Contains(file.Name)) + { + invalid.Add((item, Localization.Get("ImportPendingDelete"))); + continue; + } - if (_ignoredFonts.Contains(file.Name)) - { - invalid.Add((item, Localization.Get("ImportPendingDelete"))); - continue; - } + // For WOFF files we can attempt to convert the file to OTF before loading + StorageFile src = file; + var convertResult = await FontConverter.TryConvertAsync(file); + if (convertResult.Result is not ConversionStatus.OK) + { + invalid.Add((src, Localization.Get("ImportFailedWoff"))); + continue; + } + else + file = convertResult.File; - // For WOFF files we can attempt to convert the file to OTF before loading - StorageFile src = file; - var convertResult = await FontConverter.TryConvertAsync(file); - if (convertResult.Result is not ConversionStatus.OK) - { - invalid.Add((src, Localization.Get("ImportFailedWoff"))); - continue; - } - else - file = convertResult.File; + if (SupportedFormats.Contains(file.FileType.ToLower())) + { + // TODO : What do we do if a font with the same file name already exists? - if (SupportedFormats.Contains(file.FileType.ToLower())) + /* Explicitly not using StorageFile/Folder API's here because there + * are some strange import bugs when checking a file exists */ + if (!File.Exists(Path.Combine(_importFolder.Path, file.Name))) { - // TODO : What do we do if a font with the same file name already exists? - - /* Explicitly not using StorageFile/Folder API's here because there - * are some strange import bugs when checking a file exists */ - if (!File.Exists(Path.Combine(_importFolder.Path, file.Name))) + StorageFile fontFile; + try { - StorageFile fontFile; - try - { - /* Copy to local folder. We can only verify font file when it's inside - * the App's Local folder due to CanvasFontSet file restrictions */ - fontFile = await file.CopyAsync(_importFolder); - } - catch (Exception) - { - invalid.Add((file, Localization.Get("ImportFileCopyFail"))); - continue; - } + /* Copy to local folder. We can only verify font file when it's inside + * the App's Local folder due to CanvasFontSet file restrictions */ + fontFile = await file.CopyAsync(_importFolder); + } + catch (Exception) + { + invalid.Add((file, Localization.Get("ImportFileCopyFail"))); + continue; + } - + - // Addressing https://github.com/character-map-uwp/Character-Map-UWP/issues/241 - async Task HandleInvalidAsync() - { - invalid.Add((file, Localization.Get("ImportUnsupportedFontType"))); - await fontFile.DeleteAsync(StorageDeleteOption.PermanentDelete); - } - try + // Addressing https://github.com/character-map-uwp/Character-Map-UWP/issues/241 + async Task HandleInvalidAsync() + { + invalid.Add((file, Localization.Get("ImportUnsupportedFontType"))); + await fontFile.DeleteAsync(StorageDeleteOption.PermanentDelete); + } + try + { + /* Avoid Garbage Collection (?) issue preventing immediate file deletion + * by dropping to C++ */ + if (DirectWrite.HasValidFonts(new Uri(GetAppPath(fontFile)))) { - /* Avoid Garbage Collection (?) issue preventing immediate file deletion - * by dropping to C++ */ - if (DirectWrite.HasValidFonts(new Uri(GetAppPath(fontFile)))) - { - imported.Add(fontFile); - } - else - { - await HandleInvalidAsync(); - } + imported.Add(fontFile); } - catch (Exception) + else { await HandleInvalidAsync(); } - } - else + catch (Exception) { - existing.Add(file); + await HandleInvalidAsync(); } + } else - invalid.Add((file, Localization.Get("ImportUnsupportedFileType"))); + { + existing.Add(file); + } } + else + invalid.Add((file, Localization.Get("ImportUnsupportedFileType"))); + } - if (imported.Count > 0) - { - await LoadFontsAsync(); - } + if (imported.Count > 0) + { + await LoadFontsAsync(); + } - return new FontImportResult(imported, existing, invalid); - }); - } + return new FontImportResult(imported, existing, invalid); + }); + } - + - /// - /// Returns true if all fonts were deleted. - /// Returns false is some failed - these will be deleted on next start up. - /// - /// - /// - internal static async Task RemoveFontAsync(InstalledFont font) - { - Fonts = null; - font.PrepareForDelete(); + /// + /// Returns true if all fonts were deleted. + /// Returns false is some failed - these will be deleted on next start up. + /// + /// + /// + internal static async Task RemoveFontAsync(InstalledFont font) + { + Fonts = null; + font.PrepareForDelete(); - var variants = font.Variants.Where(v => v.IsImported).ToList(); - var success = true; + var variants = font.Variants.Where(v => v.IsImported).ToList(); + var success = true; - foreach (var variant in variants) - { - variant.Dispose(); + foreach (var variant in variants) + { + variant.Dispose(); - GC.Collect(); // required to prevent "File in use" exception + GC.Collect(); // required to prevent "File in use" exception - try - { - if (await _importFolder.TryGetItemAsync(variant.FileName) - is StorageFile file) - { - await file.DeleteAsync(StorageDeleteOption.PermanentDelete); - } - } - catch + try + { + if (await _importFolder.TryGetItemAsync(variant.FileName) + is StorageFile file) { - _ignoredFonts.Add(variant.FileName); - success = false; + await file.DeleteAsync(StorageDeleteOption.PermanentDelete); } } - - /* If we did get a "File In Use" or something similar, - * make sure we delete the file during the next start up */ - if (success == false) + catch { - var pending = await ApplicationData.Current.LocalCacheFolder.CreateFileAsync(PENDING, CreationCollisionOption.OpenIfExists); - await FileIO.WriteLinesAsync(pending, _ignoredFonts); + _ignoredFonts.Add(variant.FileName); + success = false; } + } - await LoadFontsAsync(); - return success; + /* If we did get a "File In Use" or something similar, + * make sure we delete the file during the next start up */ + if (success == false) + { + var pending = await ApplicationData.Current.LocalCacheFolder.CreateFileAsync(PENDING, CreationCollisionOption.OpenIfExists); + await FileIO.WriteLinesAsync(pending, _ignoredFonts); } - private static async Task TryGetFileAsync(string path) + await LoadFontsAsync(); + return success; + } + + private static async Task TryGetFileAsync(string path) + { + try { - try - { - return await StorageFile.GetFileFromPathAsync(path).AsTask().ConfigureAwait(false); - } - catch - { - return null; - } + return await StorageFile.GetFileFromPathAsync(path).AsTask().ConfigureAwait(false); + } + catch + { + return null; } + } - private static Task CleanUpPendingDeletesAsync() + private static Task CleanUpPendingDeletesAsync() + { + return Task.Run(async() => { - return Task.Run(async() => + /* If we fail to delete a font at runtime, delete it now before we load anything */ + var path = Path.Combine(ApplicationData.Current.LocalCacheFolder.Path, PENDING); + var fontPath = ApplicationData.Current.LocalFolder.Path; + if (File.Exists(path) && await TryGetFileAsync(path).ConfigureAwait(false) is StorageFile file) { - /* If we fail to delete a font at runtime, delete it now before we load anything */ - var path = Path.Combine(ApplicationData.Current.LocalCacheFolder.Path, PENDING); - var fontPath = ApplicationData.Current.LocalFolder.Path; - if (File.Exists(path) && await TryGetFileAsync(path).ConfigureAwait(false) is StorageFile file) - { - var lines = await FileIO.ReadLinesAsync(file).AsTask().ConfigureAwait(false); + var lines = await FileIO.ReadLinesAsync(file).AsTask().ConfigureAwait(false); - List moreFails = new (); - foreach (var line in lines) + List moreFails = new (); + foreach (var line in lines) + { + if (await TryGetFileAsync(Path.Combine(fontPath, line)).ConfigureAwait(false) is StorageFile deleteFile) { - if (await TryGetFileAsync(Path.Combine(fontPath, line)).ConfigureAwait(false) is StorageFile deleteFile) + try { - try - { - await deleteFile.DeleteAsync().AsTask().ConfigureAwait(false); - } - catch (Exception) - { - moreFails.Add(line); - } + await deleteFile.DeleteAsync().AsTask().ConfigureAwait(false); + } + catch (Exception) + { + moreFails.Add(line); } } - - if (moreFails.Count > 0) - await FileIO.WriteLinesAsync(file, moreFails).AsTask().ConfigureAwait(false); - else - await file.DeleteAsync().AsTask().ConfigureAwait(false); } - }); - - } - private static Task CleanUpTempFolderAsync() + if (moreFails.Count > 0) + await FileIO.WriteLinesAsync(file, moreFails).AsTask().ConfigureAwait(false); + else + await file.DeleteAsync().AsTask().ConfigureAwait(false); + } + }); + + } + + private static Task CleanUpTempFolderAsync() + { + return Task.Run(async () => { - return Task.Run(async () => + var path = Path.Combine(_importFolder.Path, TEMP); + if (Directory.Exists(path)) { - var path = Path.Combine(_importFolder.Path, TEMP); - if (Directory.Exists(path)) + var folder = await StorageFolder.GetFolderFromPathAsync(path).AsTask().ConfigureAwait(false); + foreach (var file in await folder.GetFilesAsync().AsTask().ConfigureAwait(false)) { - var folder = await StorageFolder.GetFolderFromPathAsync(path).AsTask().ConfigureAwait(false); - foreach (var file in await folder.GetFilesAsync().AsTask().ConfigureAwait(false)) + try { - try - { - await file.DeleteAsync().AsTask().ConfigureAwait(false); - } - catch (Exception ex) - { - Debug.WriteLine(ex); - } + await file.DeleteAsync().AsTask().ConfigureAwait(false); + } + catch (Exception ex) + { + Debug.WriteLine(ex); } } - }); - - } + } + }); + + } + + internal static async Task LoadFromFileAsync(StorageFile file) + { + await InitialiseAsync().ConfigureAwait(false); + + // 1. Convert Woff or Woff2 to OTF + var convert = await FontConverter.TryConvertAsync(file); + if (convert.Result != ConversionStatus.OK) + return null; + file = convert.File; + + // 2. Copy to temp storage + var folder = await _importFolder.CreateFolderAsync(TEMP, CreationCollisionOption.OpenIfExists).AsTask().ConfigureAwait(false); + var localFile = await file.CopyAsync(folder, file.Name, NameCollisionOption.GenerateUniqueName).AsTask().ConfigureAwait(false); + + // 3. Load fonts from file + Dictionary resultList = new (); + DWriteFontSet fontSet = Utils.GetInterop().GetFonts(localFile).Inflate(); + foreach (var font in fontSet.Fonts) + AddFont(resultList, font, localFile); + + GC.Collect(); + return resultList.Count > 0 ? resultList.First().Value : null; + } - internal static async Task LoadFromFileAsync(StorageFile file) + public static Task LoadToTempFolderAsync(FolderOpenOptions options) + { + return Task.Run(async () => { - await InitialiseAsync().ConfigureAwait(false); + var folder = options.Root as StorageFolder; + Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList.AddOrReplace("PickedFolderToken", folder); - // 1. Convert Woff or Woff2 to OTF - var convert = await FontConverter.TryConvertAsync(file); - if (convert.Result != ConversionStatus.OK) - return null; - file = convert.File; + List files = new(); - // 2. Copy to temp storage - var folder = await _importFolder.CreateFolderAsync(TEMP, CreationCollisionOption.OpenIfExists).AsTask().ConfigureAwait(false); - var localFile = await file.CopyAsync(folder, file.Name, NameCollisionOption.GenerateUniqueName).AsTask().ConfigureAwait(false); + try + { + var query = folder.CreateFileQueryWithOptions( + new QueryOptions(CommonFileQuery.DefaultQuery, FontFinder.ImportFormats) + { + FolderDepth = options.Recursive ? FolderDepth.Deep : FolderDepth.Shallow, + IndexerOption = IndexerOption.UseIndexerWhenAvailable, + }); - // 3. Load fonts from file - Dictionary resultList = new (); - DWriteFontSet fontSet = Utils.GetInterop().GetFonts(localFile).Inflate(); - foreach (var font in fontSet.Fonts) - AddFont(resultList, font, localFile); + files = (await query.GetFilesAsync().AsTask(options.Token.Value)).ToList(); + } + catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException) { } - GC.Collect(); - return resultList.Count > 0 ? resultList.First().Value : null; - } + FolderContents contents = await LoadToTempFolderAsync(files, options).ConfigureAwait(false); + if (options.IsCancelled) + return contents; - public static Task LoadToTempFolderAsync(FolderOpenOptions options) - { - return Task.Run(async () => - { - var folder = options.Root as StorageFolder; - Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList.AddOrReplace("PickedFolderToken", folder); + contents.UpdateFontSet(); + return contents; + }); + } - List files = new(); + public static async Task LoadZipToTempFolderAsync(StorageFile zipFile) + { + List files = new (){ zipFile }; + var contents = await LoadToTempFolderAsync(files, new FolderOpenOptions { AllowZip = true, Root = zipFile }).ConfigureAwait(false); + contents.UpdateFontSet(); + return contents; + } - try - { - var query = folder.CreateFileQueryWithOptions( - new QueryOptions(CommonFileQuery.DefaultQuery, FontFinder.ImportFormats) - { - FolderDepth = options.Recursive ? FolderDepth.Deep : FolderDepth.Shallow, - IndexerOption = IndexerOption.UseIndexerWhenAvailable, - }); + public static Task LoadToTempFolderAsync( + IReadOnlyList files, FolderOpenOptions options, StorageFolder tempFolder = null, FolderContents contents = null) + { + return Task.Run(async () => + { + await InitialiseAsync().ConfigureAwait(false); - files = (await query.GetFilesAsync().AsTask(options.Token.Value)).ToList(); - } - catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException) { } + // 1. Create temporary storage folder + var dest = tempFolder ?? await ApplicationData.Current.TemporaryFolder.CreateFolderAsync("i", CreationCollisionOption.GenerateUniqueName) + .AsTask().ConfigureAwait(false); + contents ??= new(options.Root, dest); + Func>> func = (storageFile) => GetFontsAsync(storageFile, dest, options); + if (options.IsCancelled) + return contents; - FolderContents contents = await LoadToTempFolderAsync(files, options).ConfigureAwait(false); - if (options.IsCancelled) - return contents; + // 2. Copy all files to temporary storage + List>> tasks = files + .Where(f => ImportFormats.Contains(f.FileType.ToLower())) + .Select(func) + .ToList(); + await Task.WhenAll(tasks).ConfigureAwait(false); - contents.UpdateFontSet(); + if (options.IsCancelled) return contents; - }); - } - public static async Task LoadZipToTempFolderAsync(StorageFile zipFile) - { - List files = new (){ zipFile }; - var contents = await LoadToTempFolderAsync(files, new FolderOpenOptions { AllowZip = true, Root = zipFile }).ConfigureAwait(false); - contents.UpdateFontSet(); - return contents; - } + // 3. Create font sets + var interop = Utils.GetInterop(); + var results = tasks.Where(t => t.Result is not null).SelectMany(t => t.Result).ToList(); + var dwSets = interop.GetFonts(results).ToList(); - public static Task LoadToTempFolderAsync( - IReadOnlyList files, FolderOpenOptions options, StorageFolder tempFolder = null, FolderContents contents = null) - { - return Task.Run(async () => + // 4. Create InstalledFonts list + for (int i = 0; i < dwSets.Count; i++) { - await InitialiseAsync().ConfigureAwait(false); - - // 1. Create temporary storage folder - var dest = tempFolder ?? await ApplicationData.Current.TemporaryFolder.CreateFolderAsync("i", CreationCollisionOption.GenerateUniqueName) - .AsTask().ConfigureAwait(false); - contents ??= new(options.Root, dest); - Func>> func = (storageFile) => GetFontsAsync(storageFile, dest, options); - if (options.IsCancelled) - return contents; - - // 2. Copy all files to temporary storage - List>> tasks = files - .Where(f => ImportFormats.Contains(f.FileType.ToLower())) - .Select(func) - .ToList(); - await Task.WhenAll(tasks).ConfigureAwait(false); + StorageFile file = results[i]; + DWriteFontSet set = dwSets[i]; - if (options.IsCancelled) - return contents; + foreach (DWriteFontFace font in set.Inflate().Fonts) + AddFont(contents.FontCache, font, file); + } - // 3. Create font sets - var interop = Utils.GetInterop(); - var results = tasks.Where(t => t.Result is not null).SelectMany(t => t.Result).ToList(); - var dwSets = interop.GetFonts(results).ToList(); + return contents; + }); + + } - // 4. Create InstalledFonts list - for (int i = 0; i < dwSets.Count; i++) - { - StorageFile file = results[i]; - DWriteFontSet set = dwSets[i]; + private static async Task> GetFontsAsync(StorageFile f, StorageFolder dest, FolderOpenOptions options) + { + if (options.IsCancelled) + return new(); - foreach (DWriteFontFace font in set.Inflate().Fonts) - AddFont(contents.FontCache, font, file); - } + List results = new(); - return contents; - }); - + if (f.FileType == ".zip") + { + if (options.AllowZip) + results = await FontConverter.ExtractFontsFromZipAsync(f, dest, options).ConfigureAwait(false); } - - private static async Task> GetFontsAsync(StorageFile f, StorageFolder dest, FolderOpenOptions options) + else { - if (options.IsCancelled) - return new(); + var convert = await FontConverter.TryConvertAsync(f, dest).ConfigureAwait(false); + if (convert.Result is not ConversionStatus.OK || options.IsCancelled) + goto Exit; - List results = new(); - - if (f.FileType == ".zip") + if (convert.File == f) { - if (options.AllowZip) - results = await FontConverter.ExtractFontsFromZipAsync(f, dest, options).ConfigureAwait(false); + var file = await f.CopyAsync(dest, f.Name, NameCollisionOption.GenerateUniqueName).AsTask().ConfigureAwait(false); + results.Add(file); } else - { - var convert = await FontConverter.TryConvertAsync(f, dest).ConfigureAwait(false); - if (convert.Result is not ConversionStatus.OK || options.IsCancelled) - goto Exit; - - if (convert.File == f) - { - var file = await f.CopyAsync(dest, f.Name, NameCollisionOption.GenerateUniqueName).AsTask().ConfigureAwait(false); - results.Add(file); - } - else - results.Add(convert.File); - } - - Exit: - options?.Increment(results.Count); - return results; + results.Add(convert.File); } - public static bool IsMDL2(FontVariant variant) => variant != null && ( - variant.FamilyName.Contains("MDL2") || variant.FamilyName.Contains("Fluent Icons")); - - public static bool IsSystemSymbolFamily(FontVariant variant) => variant != null && ( - variant.FamilyName.Equals("Segoe MDL2 Assets") || variant.FamilyName.Equals("Segoe Fluent Icons")); + Exit: + options?.Increment(results.Count); + return results; } + + public static bool IsMDL2(FontVariant variant) => variant != null && ( + variant.FamilyName.Contains("MDL2") || variant.FamilyName.Contains("Fluent Icons")); + + public static bool IsSystemSymbolFamily(FontVariant variant) => variant != null && ( + variant.FamilyName.Equals("Segoe MDL2 Assets") || variant.FamilyName.Equals("Segoe Fluent Icons")); } \ No newline at end of file diff --git a/CharacterMap/CharacterMap/Core/FontVariant.cs b/CharacterMap/CharacterMap/Core/FontVariant.cs index 27e7a9b8..eedb939d 100644 --- a/CharacterMap/CharacterMap/Core/FontVariant.cs +++ b/CharacterMap/CharacterMap/Core/FontVariant.cs @@ -2,315 +2,311 @@ using Microsoft.Graphics.Canvas.Text; using System.Globalization; -using Windows.Storage; -namespace CharacterMap.Core -{ - public record FaceMetadataInfo(string Key, string[] Values, CanvasFontInformation Info) - { - public string Value => string.Join(", ", Values); - } +namespace CharacterMap.Core; +public record FaceMetadataInfo(string Key, string[] Values, CanvasFontInformation Info) +{ + public string Value => string.Join(", ", Values); +} +[System.Diagnostics.DebuggerDisplay("{FamilyName} {PreferredName}")] +public partial class FontVariant : IDisposable +{ + /* Using a character cache avoids a lot of unnecessary allocations */ + private static Dictionary _characters { get; } = new (); - [System.Diagnostics.DebuggerDisplay("{FamilyName} {PreferredName}")] - public partial class FontVariant : IDisposable - { - /* Using a character cache avoids a lot of unnecessary allocations */ - private static Dictionary _characters { get; } = new (); + private IReadOnlyList _ranges = null; + private IReadOnlyList _fontInformation = null; + private IReadOnlyList _typographyFeatures = null; + private IReadOnlyList _xamlTypographyFeatures = null; + private FontAnalysis _analysis = null; - private IReadOnlyList _ranges = null; - private IReadOnlyList _fontInformation = null; - private IReadOnlyList _typographyFeatures = null; - private IReadOnlyList _xamlTypographyFeatures = null; - private FontAnalysis _analysis = null; + public IReadOnlyList FontInformation => _fontInformation ??= GetFontInformation(); - public IReadOnlyList FontInformation => _fontInformation ??= GetFontInformation(); + public IReadOnlyList TypographyFeatures => _typographyFeatures ??= LoadTypographyFeatures(); - public IReadOnlyList TypographyFeatures => _typographyFeatures ??= LoadTypographyFeatures(); + /// + /// Supported XAML typographer features for A SINGLE GLYPH. + /// Does not include features like Alternates which are used for strings of text. + /// + public IReadOnlyList XamlTypographyFeatures => _xamlTypographyFeatures ??= LoadTypographyFeatures(true); + + public bool HasXamlTypographyFeatures => XamlTypographyFeatures.Count > 0; - /// - /// Supported XAML typographer features for A SINGLE GLYPH. - /// Does not include features like Alternates which are used for strings of text. - /// - public IReadOnlyList XamlTypographyFeatures => _xamlTypographyFeatures ??= LoadTypographyFeatures(true); - - public bool HasXamlTypographyFeatures => XamlTypographyFeatures.Count > 0; + public CanvasFontFace FontFace => Face.FontFace; - public CanvasFontFace FontFace => Face.FontFace; + public string PreferredName { get; private set; } - public string PreferredName { get; private set; } + public IReadOnlyList Characters { get; private set; } - public IReadOnlyList Characters { get; private set; } + public double CharacterHash { get; private set; } - public double CharacterHash { get; private set; } + public bool IsImported { get; } - public bool IsImported { get; } + public string FileName { get; } - public string FileName { get; } + public string FamilyName { get; } - public string FamilyName { get; } + public CanvasUnicodeRange[] UnicodeRanges => Face.GetUnicodeRanges(); - public CanvasUnicodeRange[] UnicodeRanges => Face.GetUnicodeRanges(); + private Panose _panose = null; + public Panose Panose => _panose ??= PanoseParser.Parse(Face.Properties); - private Panose _panose = null; - public Panose Panose => _panose ??= PanoseParser.Parse(Face.Properties); + public DWriteProperties DirectWriteProperties { get; } - public DWriteProperties DirectWriteProperties { get; } + /// + /// File-system path for DWrite / XAML to construct a font for use in this application + /// + public string Source { get; } - /// - /// File-system path for DWrite / XAML to construct a font for use in this application - /// - public string Source { get; } + /// + /// A FontFamily source for XAML that includes a custom fall-back font. + /// This results in XAML *only* rendering the characters included in the font. + /// Use when you may have a scenario where characters not inside a font's glyph + /// range might be displayed, otherwise use for better performance. + /// + public string DisplaySource => $"{Source}, /Assets/AdobeBlank.otf#Adobe Blank"; - /// - /// A FontFamily source for XAML that includes a custom fall-back font. - /// This results in XAML *only* rendering the characters included in the font. - /// Use when you may have a scenario where characters not inside a font's glyph - /// range might be displayed, otherwise use for better performance. - /// - public string DisplaySource => $"{Source}, /Assets/AdobeBlank.otf#Adobe Blank"; + /// + /// Font source that external applications should use to display this font in XAML + /// + public string XamlFontSource => + (IsImported ? $"/Assets/Fonts/{FileName}#{FamilyName}" : Source); - /// - /// Font source that external applications should use to display this font in XAML - /// - public string XamlFontSource => - (IsImported ? $"/Assets/Fonts/{FileName}#{FamilyName}" : Source); + public DWriteFontFace Face { get; } - public DWriteFontFace Face { get; } + public FontVariant(DWriteFontFace face, StorageFile file) + { + DWriteProperties dwProps = face.Properties; + Face = face; + FamilyName = dwProps.FamilyName; - public FontVariant(DWriteFontFace face, StorageFile file) + if (file != null) { - DWriteProperties dwProps = face.Properties; - Face = face; - FamilyName = dwProps.FamilyName; - - if (file != null) - { - IsImported = true; - FileName = file.Name; - Source = $"{FontFinder.GetAppPath(file)}#{dwProps.FamilyName}"; - } - else - { - Source = dwProps.FamilyName; - } + IsImported = true; + FileName = file.Name; + Source = $"{FontFinder.GetAppPath(file)}#{dwProps.FamilyName}"; + } + else + { + Source = dwProps.FamilyName; + } - string name = dwProps.FaceName; - if (String.IsNullOrEmpty(name)) - name = Utils.GetVariantDescription(face); + string name = dwProps.FaceName; + if (String.IsNullOrEmpty(name)) + name = Utils.GetVariantDescription(face); - DirectWriteProperties = dwProps; - PreferredName = name; - } + DirectWriteProperties = dwProps; + PreferredName = name; + } - public string GetProviderName() - { - //if (!String.IsNullOrEmpty(DirectWriteProperties.RemoteProviderName)) - // return DirectWriteProperties.RemoteProviderName; + public string GetProviderName() + { + //if (!String.IsNullOrEmpty(DirectWriteProperties.RemoteProviderName)) + // return DirectWriteProperties.RemoteProviderName; - if (IsImported) - return Localization.Get("InstallTypeImported"); + if (IsImported) + return Localization.Get("InstallTypeImported"); - return Localization.Get($"DWriteSource{DirectWriteProperties.Source}"); - } + return Localization.Get($"DWriteSource{DirectWriteProperties.Source}"); + } - public IReadOnlyList GetRanges() - { - return _ranges ??= - GetCharacters().GroupBy(c => c.Range).Select(g => g.Key).ToList(); - } + public IReadOnlyList GetRanges() + { + return _ranges ??= + GetCharacters().GroupBy(c => c.Range).Select(g => g.Key).ToList(); + } - public IReadOnlyList GetCharacters() + public IReadOnlyList GetCharacters() + { + if (Characters == null) { - if (Characters == null) + List characters = new (); + foreach (var range in UnicodeRanges) { - List characters = new (); - foreach (var range in UnicodeRanges) - { - CharacterHash += range.First; - CharacterHash += range.Last; + CharacterHash += range.First; + CharacterHash += range.Last; - int last = (int)range.Last; - for (int i = (int)range.First; i <= last; i++) + int last = (int)range.Last; + for (int i = (int)range.First; i <= last; i++) + { + if (!_characters.TryGetValue(i, out Character c)) { - if (!_characters.TryGetValue(i, out Character c)) - { - c = new Character((uint)i); - _characters[i] = c; - } - - characters.Add(c); + c = new Character((uint)i); + _characters[i] = c; } + + characters.Add(c); } - Characters = characters; } - - return Characters; + Characters = characters; } - public int GetGlyphIndex(Character c) => Face.GetGlyphIndice(c.UnicodeIndex); + return Characters; + } + + public int GetGlyphIndex(Character c) => Face.GetGlyphIndice(c.UnicodeIndex); - public uint[] GetGlyphUnicodeIndexes() => GetCharacters().Select(c => c.UnicodeIndex).ToArray(); + public uint[] GetGlyphUnicodeIndexes() => GetCharacters().Select(c => c.UnicodeIndex).ToArray(); - public FontAnalysis GetAnalysis() => _analysis ??= TypographyAnalyzer.Analyze(this); + public FontAnalysis GetAnalysis() => _analysis ??= TypographyAnalyzer.Analyze(this); - /// - /// Load an analysis without a glyph search map. Callers later using the cached analysis and expecting a search map should - /// take care to ensure it's created by manually calling - /// - /// - private FontAnalysis GetAnalysisInternal() =>_analysis ??= TypographyAnalyzer.Analyze(this, false); + /// + /// Load an analysis without a glyph search map. Callers later using the cached analysis and expecting a search map should + /// take care to ensure it's created by manually calling + /// + /// + private FontAnalysis GetAnalysisInternal() =>_analysis ??= TypographyAnalyzer.Analyze(this, false); - /// - /// Used temporarily to allow insider builds to access COLRv1. Do not use elsewhere. Very expensive. - /// - public bool SupportsCOLRv1Rendering => Utils.Supports23H2 && DirectWriteProperties.IsColorFont && GetAnalysisInternal().SupportsCOLRv1; + /// + /// Used temporarily to allow insider builds to access COLRv1. Do not use elsewhere. Very expensive. + /// + public bool SupportsCOLRv1Rendering => Utils.Supports23H2 && DirectWriteProperties.IsColorFont && GetAnalysisInternal().SupportsCOLRv1; - /// - /// Hack used for QuickCompare - we show ALL colour fonts using manual DirectWrite rendering (using DirectText control) rather than - /// XAML TextBlock. We cannot use the flag above to filter only COLRv1 fonts as the FontAnalysis object requires actually opening and - /// manually parsing the font file headers - too expensive an operation to perform when scrolling the entire font list on the UI thread. - /// /// - public bool SupportsColourRendering => Utils.Supports23H2 && DirectWriteProperties.IsColorFont; + /// + /// Hack used for QuickCompare - we show ALL colour fonts using manual DirectWrite rendering (using DirectText control) rather than + /// XAML TextBlock. We cannot use the flag above to filter only COLRv1 fonts as the FontAnalysis object requires actually opening and + /// manually parsing the font file headers - too expensive an operation to perform when scrolling the entire font list on the UI thread. + /// /// + public bool SupportsColourRendering => Utils.Supports23H2 && DirectWriteProperties.IsColorFont; - public string TryGetSampleText() => ReadInfoKey(CanvasFontInformation.SampleText)?.Value; + public string TryGetSampleText() => ReadInfoKey(CanvasFontInformation.SampleText)?.Value; - /// - /// Attempts to return the value of . If it fails, - /// is returned instead. - /// - /// - public string TryGetFullName() => TryGetInfo(CanvasFontInformation.FullName)?.Value ?? PreferredName; + /// + /// Attempts to return the value of . If it fails, + /// is returned instead. + /// + /// + public string TryGetFullName() => TryGetInfo(CanvasFontInformation.FullName)?.Value ?? PreferredName; - public bool HasDesignScriptTag(string tag) => TryGetInfo(CanvasFontInformation.DesignScriptLanguageTag)?.Values.Contains(tag) ?? false; + public bool HasDesignScriptTag(string tag) => TryGetInfo(CanvasFontInformation.DesignScriptLanguageTag)?.Values.Contains(tag) ?? false; - /* SEARCHING */ + /* SEARCHING */ - public Dictionary SearchMap { get; set; } + public Dictionary SearchMap { get; set; } - public string GetDescription(Character c) - { - if (SearchMap == null - || !SearchMap.TryGetValue(c, out string mapping) - || string.IsNullOrWhiteSpace(mapping)) - return GlyphService.GetCharacterDescription(c.UnicodeIndex, this); + public string GetDescription(Character c) + { + if (SearchMap == null + || !SearchMap.TryGetValue(c, out string mapping) + || string.IsNullOrWhiteSpace(mapping)) + return GlyphService.GetCharacterDescription(c.UnicodeIndex, this); - return GlyphService.TryGetAGLFNName(mapping); - } + return GlyphService.TryGetAGLFNName(mapping); + } - /* INTERNAL */ + /* INTERNAL */ - private IReadOnlyList LoadTypographyFeatures(bool isXaml = false) - { - var features = TypographyAnalyzer.GetSupportedTypographyFeatures(this); + private IReadOnlyList LoadTypographyFeatures(bool isXaml = false) + { + var features = TypographyAnalyzer.GetSupportedTypographyFeatures(this); - var xaml = features.Where(f => TypographyBehavior.IsXamlSingleGlyphSupported(f.Feature)).ToList(); - if (xaml.Count > 0) - xaml.Insert(0, TypographyFeatureInfo.None); - _xamlTypographyFeatures = xaml; + var xaml = features.Where(f => TypographyBehavior.IsXamlSingleGlyphSupported(f.Feature)).ToList(); + if (xaml.Count > 0) + xaml.Insert(0, TypographyFeatureInfo.None); + _xamlTypographyFeatures = xaml; - if (features.Count > 0) - features.Insert(0, TypographyFeatureInfo.None); - _typographyFeatures = features; + if (features.Count > 0) + features.Insert(0, TypographyFeatureInfo.None); + _typographyFeatures = features; - return isXaml ? _xamlTypographyFeatures : _typographyFeatures; - } + return isXaml ? _xamlTypographyFeatures : _typographyFeatures; + } - private List GetFontInformation() - => INFORMATIONS.Select(ReadInfoKey) - .Where(s => s != null && !string.IsNullOrWhiteSpace(s.Value)) - .ToList(); - - /// - /// Reads an info key from the underlying DWriteFontFace - /// - /// - /// - /// - private FaceMetadataInfo ReadInfoKey(CanvasFontInformation info) - { - var infos = Face.GetInformationalStrings(info); - if (infos.Count == 0) - return null; - - var name = info.Humanise(); - var dic = infos.ToDictionary(k => k.Key, k => k.Value); - if (infos.TryGetValue(CultureInfo.CurrentCulture.Name, out string value) - || infos.TryGetValue("en-us", out value)) - return new (name, new string[1] { value }, info); - - return new( - name, - info is CanvasFontInformation.DesignScriptLanguageTag - ? infos.Select(i => UnicodeScriptTags.GetName(i.Value)).ToArray() - : infos.Select(i => i.Value).ToArray(), - info); - } + private List GetFontInformation() + => INFORMATIONS.Select(ReadInfoKey) + .Where(s => s != null && !string.IsNullOrWhiteSpace(s.Value)) + .ToList(); + + /// + /// Reads an info key from the underlying DWriteFontFace + /// + /// + /// + /// + private FaceMetadataInfo ReadInfoKey(CanvasFontInformation info) + { + var infos = Face.GetInformationalStrings(info); + if (infos.Count == 0) + return null; - /// - /// Attempts to return a cached info key, or load it from scratch. - /// - /// - /// - public FaceMetadataInfo TryGetInfo(CanvasFontInformation cfi) - { - if (_fontInformation is not null && _fontInformation.FirstOrDefault(p => p.Info == cfi) - is { } info) - return info; + var name = info.Humanise(); + var dic = infos.ToDictionary(k => k.Key, k => k.Value); + if (infos.TryGetValue(CultureInfo.CurrentCulture.Name, out string value) + || infos.TryGetValue("en-us", out value)) + return new (name, new string[1] { value }, info); + + return new( + name, + info is CanvasFontInformation.DesignScriptLanguageTag + ? infos.Select(i => UnicodeScriptTags.GetName(i.Value)).ToArray() + : infos.Select(i => i.Value).ToArray(), + info); + } - if (ReadInfoKey(cfi) is { } faceInfo) - return faceInfo; + /// + /// Attempts to return a cached info key, or load it from scratch. + /// + /// + /// + public FaceMetadataInfo TryGetInfo(CanvasFontInformation cfi) + { + if (_fontInformation is not null && _fontInformation.FirstOrDefault(p => p.Info == cfi) + is { } info) + return info; - return null; - } + if (ReadInfoKey(cfi) is { } faceInfo) + return faceInfo; + return null; + } - /* .NET */ - public void Dispose() - { - FontFace?.Dispose(); - //FontFace = null; - } + /* .NET */ - public override string ToString() - { - return PreferredName; - } + public void Dispose() + { + FontFace?.Dispose(); + //FontFace = null; + } + + public override string ToString() + { + return PreferredName; } +} - public partial class FontVariant +public partial class FontVariant +{ + public static FontVariant CreateDefault(DWriteFontFace face) { - public static FontVariant CreateDefault(DWriteFontFace face) + return new FontVariant(face, null) { - return new FontVariant(face, null) - { - PreferredName = "", - Characters = new List { new (0) } - }; - } - - private static CanvasFontInformation[] INFORMATIONS { get; } = { - CanvasFontInformation.FullName, - CanvasFontInformation.Description, - CanvasFontInformation.VersionStrings, - CanvasFontInformation.DesignScriptLanguageTag, - CanvasFontInformation.Designer, - CanvasFontInformation.DesignerUrl, - CanvasFontInformation.FontVendorUrl, - CanvasFontInformation.Manufacturer, - CanvasFontInformation.Trademark, - CanvasFontInformation.CopyrightNotice, - CanvasFontInformation.LicenseInfoUrl, - CanvasFontInformation.LicenseDescription, + PreferredName = "", + Characters = new List { new (0) } }; } + + private static CanvasFontInformation[] INFORMATIONS { get; } = { + CanvasFontInformation.FullName, + CanvasFontInformation.Description, + CanvasFontInformation.VersionStrings, + CanvasFontInformation.DesignScriptLanguageTag, + CanvasFontInformation.Designer, + CanvasFontInformation.DesignerUrl, + CanvasFontInformation.FontVendorUrl, + CanvasFontInformation.Manufacturer, + CanvasFontInformation.Trademark, + CanvasFontInformation.CopyrightNotice, + CanvasFontInformation.LicenseInfoUrl, + CanvasFontInformation.LicenseDescription, + }; } diff --git a/CharacterMap/CharacterMap/Core/Index.cs b/CharacterMap/CharacterMap/Core/Index.cs index 9d8ae639..805a8bda 100644 --- a/CharacterMap/CharacterMap/Core/Index.cs +++ b/CharacterMap/CharacterMap/Core/Index.cs @@ -5,244 +5,241 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.CompilerServices; - -namespace System +namespace System; + +/// Represent a range has start and end indexes. +/// +/// Range is used by the C# compiler to support the range syntax. +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; +/// int[] subArray1 = someArray[0..2]; // { 1, 2 } +/// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } +/// +/// +internal readonly struct Range : IEquatable { - /// Represent a range has start and end indexes. - /// - /// Range is used by the C# compiler to support the range syntax. - /// - /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; - /// int[] subArray1 = someArray[0..2]; // { 1, 2 } - /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } - /// - /// - internal readonly struct Range : IEquatable - { - /// Represent the inclusive start index of the Range. - public Index Start { get; } + /// Represent the inclusive start index of the Range. + public Index Start { get; } - /// Represent the exclusive end index of the Range. - public Index End { get; } + /// Represent the exclusive end index of the Range. + public Index End { get; } - /// Construct a Range object using the start and end indexes. - /// Represent the inclusive start index of the range. - /// Represent the exclusive end index of the range. - public Range(Index start, Index end) - { - Start = start; - End = end; - } + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + public Range(Index start, Index end) + { + Start = start; + End = end; + } - /// Indicates whether the current Range object is equal to another object of the same type. - /// An object to compare with this object - public override bool Equals(object? value) => - value is Range r && - r.Start.Equals(Start) && - r.End.Equals(End); + /// Indicates whether the current Range object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals(object? value) => + value is Range r && + r.Start.Equals(Start) && + r.End.Equals(End); - /// Indicates whether the current Range object is equal to another Range object. - /// An object to compare with this object - public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); + /// Indicates whether the current Range object is equal to another Range object. + /// An object to compare with this object + public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); - /// Returns the hash code for this instance. - public override int GetHashCode() - { - return Start.GetHashCode() * 31 + End.GetHashCode(); - } + /// Returns the hash code for this instance. + public override int GetHashCode() + { + return Start.GetHashCode() * 31 + End.GetHashCode(); + } - /// Converts the value of the current Range object to its equivalent string representation. - public override string ToString() - { - return Start + ".." + End; - } + /// Converts the value of the current Range object to its equivalent string representation. + public override string ToString() + { + return Start + ".." + End; + } - /// Create a Range object starting from start index to the end of the collection. - public static Range StartAt(Index start) => new Range(start, Index.End); + /// Create a Range object starting from start index to the end of the collection. + public static Range StartAt(Index start) => new Range(start, Index.End); - /// Create a Range object starting from first element in the collection to the end Index. - public static Range EndAt(Index end) => new Range(Index.Start, end); + /// Create a Range object starting from first element in the collection to the end Index. + public static Range EndAt(Index end) => new Range(Index.Start, end); - /// Create a Range object starting from first element to the end. - public static Range All => new Range(Index.Start, Index.End); + /// Create a Range object starting from first element to the end. + public static Range All => new Range(Index.Start, Index.End); - /// Calculate the start offset and length of range object using a collection length. - /// The length of the collection that the range will be used with. length has to be a positive value. - /// - /// For performance reason, we don't validate the input length parameter against negative values. - /// It is expected Range will be used with collections which always have non negative length/count. - /// We validate the range is inside the length scope though. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public (int Offset, int Length) GetOffsetAndLength(int length) + /// Calculate the start offset and length of range object using a collection length. + /// The length of the collection that the range will be used with. length has to be a positive value. + /// + /// For performance reason, we don't validate the input length parameter against negative values. + /// It is expected Range will be used with collections which always have non negative length/count. + /// We validate the range is inside the length scope though. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public (int Offset, int Length) GetOffsetAndLength(int length) + { + int start; + var startIndex = Start; + if (startIndex.IsFromEnd) + start = length - startIndex.Value; + else + start = startIndex.Value; + + int end; + var endIndex = End; + if (endIndex.IsFromEnd) + end = length - endIndex.Value; + else + end = endIndex.Value; + + if ((uint)end > (uint)length || (uint)start > (uint)end) { - int start; - var startIndex = Start; - if (startIndex.IsFromEnd) - start = length - startIndex.Value; - else - start = startIndex.Value; - - int end; - var endIndex = End; - if (endIndex.IsFromEnd) - end = length - endIndex.Value; - else - end = endIndex.Value; - - if ((uint)end > (uint)length || (uint)start > (uint)end) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } - - return (start, end - start); + throw new ArgumentOutOfRangeException(nameof(length)); } + + return (start, end - start); } +} + +/// Represent a type can be used to index a collection either from the start or the end. +/// +/// Index is used by the C# compiler to support the new index syntax +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; +/// int lastElement = someArray[^1]; // lastElement = 5 +/// +/// +internal readonly struct Index : global::System.IEquatable +{ + private readonly int _value; - /// Represent a type can be used to index a collection either from the start or the end. + /// Construct an Index using a value and indicating if the index is from the start or from the end. + /// The index value. it has to be zero or positive number. + /// Indicating if the index is from the start or from the end. /// - /// Index is used by the C# compiler to support the new index syntax - /// - /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; - /// int lastElement = someArray[^1]; // lastElement = 5 - /// + /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. /// - internal readonly struct Index : global::System.IEquatable + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public Index(int value, bool fromEnd = false) { - private readonly int _value; - - /// Construct an Index using a value and indicating if the index is from the start or from the end. - /// The index value. it has to be zero or positive number. - /// Indicating if the index is from the start or from the end. - /// - /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - public Index(int value, bool fromEnd = false) + if (value < 0) { - if (value < 0) - { - global::System.Index.ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException(); - } - - if (fromEnd) - _value = ~value; - else - _value = value; + global::System.Index.ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException(); } - // The following private constructors mainly created for perf reason to avoid the checks - private Index(int value) - { + if (fromEnd) + _value = ~value; + else _value = value; - } + } + + // The following private constructors mainly created for perf reason to avoid the checks + private Index(int value) + { + _value = value; + } - /// Create an Index pointing at first element. - public static global::System.Index Start => new global::System.Index(0); + /// Create an Index pointing at first element. + public static global::System.Index Start => new global::System.Index(0); - /// Create an Index pointing at beyond last element. - public static global::System.Index End => new global::System.Index(~0); + /// Create an Index pointing at beyond last element. + public static global::System.Index End => new global::System.Index(~0); - /// Create an Index from the start at the position indicated by the value. - /// The index value from the start. - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - public static global::System.Index FromStart(int value) + /// Create an Index from the start at the position indicated by the value. + /// The index value from the start. + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static global::System.Index FromStart(int value) + { + if (value < 0) { - if (value < 0) - { - global::System.Index.ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException(); - } - - return new global::System.Index(value); + global::System.Index.ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException(); } - /// Create an Index from the end at the position indicated by the value. - /// The index value from the end. - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - public static global::System.Index FromEnd(int value) - { - if (value < 0) - { - global::System.Index.ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException(); - } + return new global::System.Index(value); + } - return new global::System.Index(~value); + /// Create an Index from the end at the position indicated by the value. + /// The index value from the end. + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static global::System.Index FromEnd(int value) + { + if (value < 0) + { + global::System.Index.ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException(); } - /// Returns the index value. - public int Value + return new global::System.Index(~value); + } + + /// Returns the index value. + public int Value + { + get { - get - { - if (_value < 0) - return ~_value; - else - return _value; - } + if (_value < 0) + return ~_value; + else + return _value; } + } - /// Indicates whether the index is from the start or the end. - public bool IsFromEnd => _value < 0; - - /// Calculate the offset from the start using the giving collection length. - /// The length of the collection that the Index will be used with. length has to be a positive value - /// - /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. - /// we don't validate either the returned offset is greater than the input length. - /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and - /// then used to index a collection will get out of range exception which will be same affect as the validation. - /// - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - public int GetOffset(int length) + /// Indicates whether the index is from the start or the end. + public bool IsFromEnd => _value < 0; + + /// Calculate the offset from the start using the giving collection length. + /// The length of the collection that the Index will be used with. length has to be a positive value + /// + /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. + /// we don't validate either the returned offset is greater than the input length. + /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and + /// then used to index a collection will get out of range exception which will be same affect as the validation. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public int GetOffset(int length) + { + int offset = _value; + if (IsFromEnd) { - int offset = _value; - if (IsFromEnd) - { - // offset = length - (~value) - // offset = length + (~(~value) + 1) - // offset = length + value + 1 - - offset += length + 1; - } - return offset; + // offset = length - (~value) + // offset = length + (~(~value) + 1) + // offset = length + value + 1 + + offset += length + 1; } + return offset; + } - /// Indicates whether the current Index object is equal to another object of the same type. - /// An object to compare with this object - public override bool Equals(object? value) => value is global::System.Index && _value == ((global::System.Index)value)._value; + /// Indicates whether the current Index object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals(object? value) => value is global::System.Index && _value == ((global::System.Index)value)._value; - /// Indicates whether the current Index object is equal to another Index object. - /// An object to compare with this object - public bool Equals(global::System.Index other) => _value == other._value; + /// Indicates whether the current Index object is equal to another Index object. + /// An object to compare with this object + public bool Equals(global::System.Index other) => _value == other._value; - /// Returns the hash code for this instance. - public override int GetHashCode() => _value; + /// Returns the hash code for this instance. + public override int GetHashCode() => _value; - /// Converts integer number to an Index. - public static implicit operator global::System.Index(int value) => FromStart(value); + /// Converts integer number to an Index. + public static implicit operator global::System.Index(int value) => FromStart(value); - /// Converts the value of the current Index object to its equivalent string representation. - public override string ToString() - { - if (IsFromEnd) - return ToStringFromEnd(); + /// Converts the value of the current Index object to its equivalent string representation. + public override string ToString() + { + if (IsFromEnd) + return ToStringFromEnd(); - return ((uint)Value).ToString(); - } + return ((uint)Value).ToString(); + } - private string ToStringFromEnd() - { - return '^' + Value.ToString(); - } + private string ToStringFromEnd() + { + return '^' + Value.ToString(); + } - private static class ThrowHelper + private static class ThrowHelper + { + //[global::System.Diagnostics.CodeAnalysis.DoesNotReturn] + public static void ThrowValueArgumentOutOfRange_NeedNonNegNumException() { - //[global::System.Diagnostics.CodeAnalysis.DoesNotReturn] - public static void ThrowValueArgumentOutOfRange_NeedNonNegNumException() - { - throw new global::System.ArgumentOutOfRangeException("value", "Non-negative number required."); - } + throw new global::System.ArgumentOutOfRangeException("value", "Non-negative number required."); } } } diff --git a/CharacterMap/CharacterMap/Core/InstalledFont.cs b/CharacterMap/CharacterMap/Core/InstalledFont.cs index 96e8d175..9eaddad3 100644 --- a/CharacterMap/CharacterMap/Core/InstalledFont.cs +++ b/CharacterMap/CharacterMap/Core/InstalledFont.cs @@ -1,115 +1,105 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using CharacterMapCX; -using Microsoft.Graphics.Canvas.Text; -using Windows.Storage; -using Windows.UI.Text; - -namespace CharacterMap.Core +namespace CharacterMap.Core; + +public class InstalledFont : IComparable, IEquatable { - public class InstalledFont : IComparable, IEquatable - { - private List _variants; + private List _variants; - public string Name { get; } + public string Name { get; } - public bool IsSymbolFont => _variants[0].DirectWriteProperties.IsSymbolFont; + public bool IsSymbolFont => _variants[0].DirectWriteProperties.IsSymbolFont; - public IList Variants => _variants; + public IList Variants => _variants; - public bool HasVariants => _variants.Count > 1; + public bool HasVariants => _variants.Count > 1; - public bool HasImportedFiles { get; private set; } + public bool HasImportedFiles { get; private set; } - private FontVariant _defaultVariant; - public FontVariant DefaultVariant => _defaultVariant ??= Utils.GetDefaultVariant(Variants); + private FontVariant _defaultVariant; + public FontVariant DefaultVariant => _defaultVariant ??= Utils.GetDefaultVariant(Variants); - private InstalledFont(string name) - { - Name = name; - _variants = new (); - } + private InstalledFont(string name) + { + Name = name; + _variants = new (); + } - public InstalledFont(string name, DWriteFontFace face, StorageFile file = null) : this(name) - { - AddVariant(face, file); - } + public InstalledFont(string name, DWriteFontFace face, StorageFile file = null) : this(name) + { + AddVariant(face, file); + } - public void AddVariant(DWriteFontFace fontFace, StorageFile file = null) - { - _variants.Add(new (fontFace, file)); - if (file != null) - HasImportedFiles = true; + public void AddVariant(DWriteFontFace fontFace, StorageFile file = null) + { + _variants.Add(new (fontFace, file)); + if (file != null) + HasImportedFiles = true; - _defaultVariant = null; - } + _defaultVariant = null; + } - public void SortVariants() - { - _variants = _variants.OrderBy(v => v.DirectWriteProperties.Weight.Weight).ToList(); - } + public void SortVariants() + { + _variants = _variants.OrderBy(v => v.DirectWriteProperties.Weight.Weight).ToList(); + } - public void PrepareForDelete() - { - //FontFace = null; - } + public void PrepareForDelete() + { + //FontFace = null; + } - public InstalledFont Clone() - { - return new InstalledFont(this.Name) - { - _variants = this._variants.ToList(), - HasImportedFiles = this.HasImportedFiles - }; - } - - public static InstalledFont CreateDefault(DWriteFontFace face) + public InstalledFont Clone() + { + return new InstalledFont(this.Name) { - var font = new InstalledFont(""); - font._variants.Add(FontVariant.CreateDefault(face)); - return font; - } + _variants = this._variants.ToList(), + HasImportedFiles = this.HasImportedFiles + }; + } - public int CompareTo(object obj) - { - if (obj is InstalledFont f) - return Name.CompareTo(f.Name); + public static InstalledFont CreateDefault(DWriteFontFace face) + { + var font = new InstalledFont(""); + font._variants.Add(FontVariant.CreateDefault(face)); + return font; + } - return 0; - } + public int CompareTo(object obj) + { + if (obj is InstalledFont f) + return Name.CompareTo(f.Name); - public override bool Equals(object obj) - { - return Equals(obj as InstalledFont); - } + return 0; + } - public bool Equals(InstalledFont other) - { - return other is not null && - Name == other.Name; - } + public override bool Equals(object obj) + { + return Equals(obj as InstalledFont); + } - public override int GetHashCode() - { - int hashCode = -1425556920; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Name); - hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(Variants); - hashCode = hashCode * -1521134295 + HasImportedFiles.GetHashCode(); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(DefaultVariant); - return hashCode; - } - - public static bool operator ==(InstalledFont left, InstalledFont right) - { - return EqualityComparer.Default.Equals(left, right); - } + public bool Equals(InstalledFont other) + { + return other is not null && + Name == other.Name; + } - public static bool operator !=(InstalledFont left, InstalledFont right) - { - return !(left == right); - } + public override int GetHashCode() + { + int hashCode = -1425556920; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Name); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(Variants); + hashCode = hashCode * -1521134295 + HasImportedFiles.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(DefaultVariant); + return hashCode; + } + + public static bool operator ==(InstalledFont left, InstalledFont right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(InstalledFont left, InstalledFont right) + { + return !(left == right); } } diff --git a/CharacterMap/CharacterMap/Core/Localizer.cs b/CharacterMap/CharacterMap/Core/Localizer.cs index 9fbd6e1e..41c16a8d 100644 --- a/CharacterMap/CharacterMap/Core/Localizer.cs +++ b/CharacterMap/CharacterMap/Core/Localizer.cs @@ -1,36 +1,34 @@ -using CharacterMap.Helpers; -using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Markup; -namespace CharacterMap.Core +namespace CharacterMap.Core; + +[MarkupExtensionReturnType(ReturnType = typeof(string))] +public class Localizer : MarkupExtension { - [MarkupExtensionReturnType(ReturnType = typeof(string))] - public class Localizer : MarkupExtension - { - public string Key { get; set; } + public string Key { get; set; } - public CharacterCasing Casing { get; set; } = CharacterCasing.Normal; + public CharacterCasing Casing { get; set; } = CharacterCasing.Normal; - public bool ZuneTitle { get; set; } + public bool ZuneTitle { get; set; } - protected override object ProvideValue() - { - string text = Localization.Get(Key); + protected override object ProvideValue() + { + string text = Localization.Get(Key); - if (ZuneTitle) - Casing = ResourceHelper.Get("TitleCasing"); + if (ZuneTitle) + Casing = ResourceHelper.Get("TitleCasing"); - if (Casing != CharacterCasing.Normal) - { - } - text = Casing switch - { - CharacterCasing.Upper => text.ToUpper(), - CharacterCasing.Lower => text.ToLower(), - _ => text - }; - - return text; + if (Casing != CharacterCasing.Normal) + { } + text = Casing switch + { + CharacterCasing.Upper => text.ToUpper(), + CharacterCasing.Lower => text.ToLower(), + _ => text + }; + + return text; } } diff --git a/CharacterMap/CharacterMap/Core/PanoseParser.cs b/CharacterMap/CharacterMap/Core/PanoseParser.cs index e055e365..210c3d18 100644 --- a/CharacterMap/CharacterMap/Core/PanoseParser.cs +++ b/CharacterMap/CharacterMap/Core/PanoseParser.cs @@ -1,13 +1,4 @@ -using Microsoft.Graphics.Canvas.Text; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using CharacterMap.Models; -using CharacterMapCX; -using System.Runtime.CompilerServices; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; namespace CharacterMap.Core { diff --git a/CharacterMap/CharacterMap/Core/Properties.cs b/CharacterMap/CharacterMap/Core/Properties.cs index 92464aac..4645c6d9 100644 --- a/CharacterMap/CharacterMap/Core/Properties.cs +++ b/CharacterMap/CharacterMap/Core/Properties.cs @@ -1,13 +1,7 @@ using CharacterMap.Controls; -using CharacterMap.Helpers; -using CharacterMap.Models; using CharacterMapCX.Controls; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Windows.Foundation.Collections; using Windows.UI.Composition; using Windows.UI.Core; @@ -21,1312 +15,1311 @@ using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Animation; -namespace CharacterMap.Core +namespace CharacterMap.Core; + +/// +/// XAML Attached Properties +/// +[Bindable] +public class Properties : DependencyObject { - /// - /// XAML Attached Properties - /// - [Bindable] - public class Properties : DependencyObject - { - #region FILTER + #region FILTER - /* Used to apply a filter to MenuFlyoutItem's on the filter list */ + /* Used to apply a filter to MenuFlyoutItem's on the filter list */ - public static BasicFontFilter GetFilter(DependencyObject obj) - { - return (BasicFontFilter)obj.GetValue(FilterProperty); - } + public static BasicFontFilter GetFilter(DependencyObject obj) + { + return (BasicFontFilter)obj.GetValue(FilterProperty); + } - public static void SetFilter(DependencyObject obj, BasicFontFilter value) - { - obj.SetValue(FilterProperty, value); - } + public static void SetFilter(DependencyObject obj, BasicFontFilter value) + { + obj.SetValue(FilterProperty, value); + } - public static readonly DependencyProperty FilterProperty = - DependencyProperty.RegisterAttached("Filter", typeof(BasicFontFilter), typeof(Properties), new PropertyMetadata(null, (d, e) => + public static readonly DependencyProperty FilterProperty = + DependencyProperty.RegisterAttached("Filter", typeof(BasicFontFilter), typeof(Properties), new PropertyMetadata(null, (d, e) => + { + if (d is MenuFlyoutItem item && e.NewValue is BasicFontFilter f) { - if (d is MenuFlyoutItem item && e.NewValue is BasicFontFilter f) - { - item.CommandParameter = f; - item.Text = f.DisplayTitle; - } - })); + item.CommandParameter = f; + item.Text = f.DisplayTitle; + } + })); - #endregion + #endregion - #region TYPOGRAPHY + #region TYPOGRAPHY - /* Helper to apply TypographyFeatureInfo to a TextBlock */ + /* Helper to apply TypographyFeatureInfo to a TextBlock */ - public static TypographyFeatureInfo GetTypography(DependencyObject obj) - { - return (TypographyFeatureInfo)obj.GetValue(TypographyProperty); - } + public static TypographyFeatureInfo GetTypography(DependencyObject obj) + { + return (TypographyFeatureInfo)obj.GetValue(TypographyProperty); + } - public static void SetTypography(DependencyObject obj, TypographyFeatureInfo value) - { - obj.SetValue(TypographyProperty, value); - } + public static void SetTypography(DependencyObject obj, TypographyFeatureInfo value) + { + obj.SetValue(TypographyProperty, value); + } - public static readonly DependencyProperty TypographyProperty = - DependencyProperty.RegisterAttached("Typography", typeof(TypographyFeatureInfo), typeof(Properties), new PropertyMetadata(null, (d, e) => + public static readonly DependencyProperty TypographyProperty = + DependencyProperty.RegisterAttached("Typography", typeof(TypographyFeatureInfo), typeof(Properties), new PropertyMetadata(null, (d, e) => + { + if (d is TextBlock t) { - if (d is TextBlock t) - { - TypographyFeatureInfo i = e.NewValue as TypographyFeatureInfo; - var x = XamlDirect.GetDefault(); - IXamlDirectObject p = x.GetXamlDirectObject(t); - CharacterGridView.UpdateTypography(x, p, i); - } - })); + TypographyFeatureInfo i = e.NewValue as TypographyFeatureInfo; + var x = XamlDirect.GetDefault(); + IXamlDirectObject p = x.GetXamlDirectObject(t); + CharacterGridView.UpdateTypography(x, p, i); + } + })); - #endregion + #endregion - #region CLIP TO BOUNDS + #region CLIP TO BOUNDS - public static bool GetClipToBounds(DependencyObject obj) - { - return (bool)obj.GetValue(ClipToBoundsProperty); - } + public static bool GetClipToBounds(DependencyObject obj) + { + return (bool)obj.GetValue(ClipToBoundsProperty); + } - public static void SetClipToBounds(DependencyObject obj, bool value) - { - obj.SetValue(ClipToBoundsProperty, value); - } + public static void SetClipToBounds(DependencyObject obj, bool value) + { + obj.SetValue(ClipToBoundsProperty, value); + } - public static readonly DependencyProperty ClipToBoundsProperty = - DependencyProperty.RegisterAttached("ClipToBounds", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d, e) => + public static readonly DependencyProperty ClipToBoundsProperty = + DependencyProperty.RegisterAttached("ClipToBounds", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d, e) => + { + if (d is FrameworkElement f) { - if (d is FrameworkElement f) - { - var v = f.GetElementVisual(); - if (e.NewValue is true) - v.Clip = v.Compositor.CreateInsetClip(); - else - v.Clip = null; + var v = f.GetElementVisual(); + if (e.NewValue is true) + v.Clip = v.Compositor.CreateInsetClip(); + else + v.Clip = null; - } - })); + } + })); - #endregion + #endregion - #region ICON + #region ICON - public static IconElement GetIcon(DependencyObject obj) - { - return (IconElement)obj.GetValue(IconProperty); - } + public static IconElement GetIcon(DependencyObject obj) + { + return (IconElement)obj.GetValue(IconProperty); + } - public static void SetIcon(DependencyObject obj, IconElement value) - { - obj.SetValue(IconProperty, value); - } + public static void SetIcon(DependencyObject obj, IconElement value) + { + obj.SetValue(IconProperty, value); + } - public static readonly DependencyProperty IconProperty = - DependencyProperty.RegisterAttached("Icon", typeof(IconElement), typeof(Properties), new PropertyMetadata(null)); + public static readonly DependencyProperty IconProperty = + DependencyProperty.RegisterAttached("Icon", typeof(IconElement), typeof(Properties), new PropertyMetadata(null)); - #endregion + #endregion - #region PopupRoot + #region PopupRoot - public static FrameworkElement GetPopupRoot(DependencyObject obj) - { - return (FrameworkElement)obj.GetValue(PopupRootProperty); - } + public static FrameworkElement GetPopupRoot(DependencyObject obj) + { + return (FrameworkElement)obj.GetValue(PopupRootProperty); + } - public static void SetPopupRoot(DependencyObject obj, FrameworkElement value) - { - obj.SetValue(PopupRootProperty, value); - } + public static void SetPopupRoot(DependencyObject obj, FrameworkElement value) + { + obj.SetValue(PopupRootProperty, value); + } - public static readonly DependencyProperty PopupRootProperty = - DependencyProperty.RegisterAttached("PopupRoot", typeof(FrameworkElement), typeof(Properties), new PropertyMetadata(0)); + public static readonly DependencyProperty PopupRootProperty = + DependencyProperty.RegisterAttached("PopupRoot", typeof(FrameworkElement), typeof(Properties), new PropertyMetadata(0)); - #endregion + #endregion - #region DEVOPTION + #region DEVOPTION - public static DevOption GetDevOption(DependencyObject obj) - { - return (DevOption)obj.GetValue(DevOptionProperty); - } + public static DevOption GetDevOption(DependencyObject obj) + { + return (DevOption)obj.GetValue(DevOptionProperty); + } - public static void SetDevOption(DependencyObject obj, DevOption value) - { - obj.SetValue(DevOptionProperty, value); - } + public static void SetDevOption(DependencyObject obj, DevOption value) + { + obj.SetValue(DevOptionProperty, value); + } - public static readonly DependencyProperty DevOptionProperty = - DependencyProperty.RegisterAttached("DevOption", typeof(DevOption), typeof(Properties), new PropertyMetadata(null)); + public static readonly DependencyProperty DevOptionProperty = + DependencyProperty.RegisterAttached("DevOption", typeof(DevOption), typeof(Properties), new PropertyMetadata(null)); - #endregion + #endregion - #region StyleKey + #region StyleKey - public static string GetStyleKey(DependencyObject obj) - { - return (string)obj.GetValue(StyleKeyProperty); - } + public static string GetStyleKey(DependencyObject obj) + { + return (string)obj.GetValue(StyleKeyProperty); + } - public static void SetStyleKey(DependencyObject obj, string value) - { - obj.SetValue(StyleKeyProperty, value); - } + public static void SetStyleKey(DependencyObject obj, string value) + { + obj.SetValue(StyleKeyProperty, value); + } - public static readonly DependencyProperty StyleKeyProperty = - DependencyProperty.RegisterAttached("StyleKey", typeof(string), typeof(Properties), new PropertyMetadata(null)); + public static readonly DependencyProperty StyleKeyProperty = + DependencyProperty.RegisterAttached("StyleKey", typeof(string), typeof(Properties), new PropertyMetadata(null)); - #endregion + #endregion - #region IsMouseInputEnabled + #region IsMouseInputEnabled - /// Enables Mouse & Touch input on an InkCanvas + /// Enables Mouse & Touch input on an InkCanvas - public static bool GetIsMouseInputEnabled(DependencyObject obj) - { - return (bool)obj.GetValue(IsMouseInputEnabledProperty); - } + public static bool GetIsMouseInputEnabled(DependencyObject obj) + { + return (bool)obj.GetValue(IsMouseInputEnabledProperty); + } - public static void SetIsMouseInputEnabled(DependencyObject obj, bool value) - { - obj.SetValue(IsMouseInputEnabledProperty, value); - } + public static void SetIsMouseInputEnabled(DependencyObject obj, bool value) + { + obj.SetValue(IsMouseInputEnabledProperty, value); + } - public static readonly DependencyProperty IsMouseInputEnabledProperty = - DependencyProperty.RegisterAttached("IsMouseInputEnabled", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d, e) => + public static readonly DependencyProperty IsMouseInputEnabledProperty = + DependencyProperty.RegisterAttached("IsMouseInputEnabled", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d, e) => + { + if (d is InkCanvas c) { - if (d is InkCanvas c) - { - if (e.NewValue is bool b && b) - c.InkPresenter.InputDeviceTypes = - CoreInputDeviceTypes.Mouse | - CoreInputDeviceTypes.Touch | - CoreInputDeviceTypes.Pen; - else - c.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Pen; - } - })); + if (e.NewValue is bool b && b) + c.InkPresenter.InputDeviceTypes = + CoreInputDeviceTypes.Mouse | + CoreInputDeviceTypes.Touch | + CoreInputDeviceTypes.Pen; + else + c.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Pen; + } + })); - #endregion + #endregion - #region DirectText Options + #region DirectText Options - // Applies CharacterRenderingOptions to a DirectText control + // Applies CharacterRenderingOptions to a DirectText control - public static CharacterRenderingOptions GetOptions(DependencyObject obj) - { - return (CharacterRenderingOptions)obj.GetValue(OptionsProperty); - } + public static CharacterRenderingOptions GetOptions(DependencyObject obj) + { + return (CharacterRenderingOptions)obj.GetValue(OptionsProperty); + } - /// - /// Applies to a control - /// - public static void SetOptions(DependencyObject obj, CharacterRenderingOptions value) - { - obj.SetValue(OptionsProperty, value); - } + /// + /// Applies to a control + /// + public static void SetOptions(DependencyObject obj, CharacterRenderingOptions value) + { + obj.SetValue(OptionsProperty, value); + } - /// - /// Applies to a or control - /// - public static readonly DependencyProperty OptionsProperty = - DependencyProperty.RegisterAttached("Options", typeof(CharacterRenderingOptions), typeof(Properties), new PropertyMetadata(null, (s, e) => + /// + /// Applies to a or control + /// + public static readonly DependencyProperty OptionsProperty = + DependencyProperty.RegisterAttached("Options", typeof(CharacterRenderingOptions), typeof(Properties), new PropertyMetadata(null, (s, e) => + { + if (s is DirectText d) { - if (s is DirectText d) + if (e.NewValue is CharacterRenderingOptions o) { - if (e.NewValue is CharacterRenderingOptions o) - { - d.Axis = o.Axis; - d.FallbackFont = Converters.GetFontFallback(); - d.FontFace = o.Variant.Face; - d.FontFamily = (FontFamily)XamlBindingHelper.ConvertValue(typeof(FontFamily), o.Variant.Source); - d.FontStretch = o.Variant.DirectWriteProperties.Stretch; - d.FontStyle = o.Variant.DirectWriteProperties.Style; - d.FontWeight = o.Variant.DirectWriteProperties.Weight; - d.IsColorFontEnabled = o.IsColourFontEnabled; - d.Typography = o.DXTypography; - } - else - { - d.ClearValue(DirectText.AxisProperty); - d.ClearValue(DirectText.FallbackFontProperty); - d.ClearValue(DirectText.FontFaceProperty); - d.ClearValue(DirectText.FontFamilyProperty); - d.ClearValue(DirectText.FontStretchProperty); - d.ClearValue(DirectText.FontStyleProperty); - d.ClearValue(DirectText.FontWeightProperty); - d.ClearValue(DirectText.IsColorFontEnabledProperty); - d.ClearValue(DirectText.TypographyProperty); - } - } - else if (s is TextBlock t) + d.Axis = o.Axis; + d.FallbackFont = Converters.GetFontFallback(); + d.FontFace = o.Variant.Face; + d.FontFamily = (FontFamily)XamlBindingHelper.ConvertValue(typeof(FontFamily), o.Variant.Source); + d.FontStretch = o.Variant.DirectWriteProperties.Stretch; + d.FontStyle = o.Variant.DirectWriteProperties.Style; + d.FontWeight = o.Variant.DirectWriteProperties.Weight; + d.IsColorFontEnabled = o.IsColourFontEnabled; + d.Typography = o.DXTypography; + } + else { - if (e.NewValue is CharacterRenderingOptions o) - { - t.FontFamily = (FontFamily)XamlBindingHelper.ConvertValue(typeof(FontFamily), o.Variant.DisplaySource); - t.FontStretch = o.Variant.DirectWriteProperties.Stretch; - t.FontStyle = o.Variant.DirectWriteProperties.Style; - t.FontWeight = o.Variant.DirectWriteProperties.Weight; - t.IsColorFontEnabled = o.IsColourFontEnabled; - SetTypography(t, o.DefaultTypography); - } - else - { - t.ClearValue(Properties.TypographyProperty); - t.ClearValue(TextBlock.FontFamilyProperty); - t.ClearValue(TextBlock.FontStretchProperty); - t.ClearValue(TextBlock.FontStyleProperty); - t.ClearValue(TextBlock.FontWeightProperty); - t.ClearValue(TextBlock.IsColorFontEnabledProperty); - } + d.ClearValue(DirectText.AxisProperty); + d.ClearValue(DirectText.FallbackFontProperty); + d.ClearValue(DirectText.FontFaceProperty); + d.ClearValue(DirectText.FontFamilyProperty); + d.ClearValue(DirectText.FontStretchProperty); + d.ClearValue(DirectText.FontStyleProperty); + d.ClearValue(DirectText.FontWeightProperty); + d.ClearValue(DirectText.IsColorFontEnabledProperty); + d.ClearValue(DirectText.TypographyProperty); + } + } + else if (s is TextBlock t) + { + if (e.NewValue is CharacterRenderingOptions o) + { + t.FontFamily = (FontFamily)XamlBindingHelper.ConvertValue(typeof(FontFamily), o.Variant.DisplaySource); + t.FontStretch = o.Variant.DirectWriteProperties.Stretch; + t.FontStyle = o.Variant.DirectWriteProperties.Style; + t.FontWeight = o.Variant.DirectWriteProperties.Weight; + t.IsColorFontEnabled = o.IsColourFontEnabled; + SetTypography(t, o.DefaultTypography); } - })); + else + { + t.ClearValue(Properties.TypographyProperty); + t.ClearValue(TextBlock.FontFamilyProperty); + t.ClearValue(TextBlock.FontStretchProperty); + t.ClearValue(TextBlock.FontStyleProperty); + t.ClearValue(TextBlock.FontWeightProperty); + t.ClearValue(TextBlock.IsColorFontEnabledProperty); + } + } + })); - #endregion + #endregion - #region DEFAULT TOOL + #region DEFAULT TOOL - /// Sets the default tool for an InkToolbar + /// Sets the default tool for an InkToolbar - public static InkToolbarToolButton GetDefaultTool(DependencyObject obj) - { - return (InkToolbarToolButton)obj.GetValue(DefaultToolProperty); - } + public static InkToolbarToolButton GetDefaultTool(DependencyObject obj) + { + return (InkToolbarToolButton)obj.GetValue(DefaultToolProperty); + } - public static void SetDefaultTool(DependencyObject obj, InkToolbarToolButton value) - { - obj.SetValue(DefaultToolProperty, value); - } + public static void SetDefaultTool(DependencyObject obj, InkToolbarToolButton value) + { + obj.SetValue(DefaultToolProperty, value); + } - public static readonly DependencyProperty DefaultToolProperty = - DependencyProperty.RegisterAttached("DefaultTool", typeof(InkToolbarToolButton), typeof(Properties), new PropertyMetadata(null, (d, e) => + public static readonly DependencyProperty DefaultToolProperty = + DependencyProperty.RegisterAttached("DefaultTool", typeof(InkToolbarToolButton), typeof(Properties), new PropertyMetadata(null, (d, e) => + { + if (d is InkToolbar t) { - if (d is InkToolbar t) - { - if (e.NewValue is InkToolbarToolButton b) - t.ActiveTool = b; - } - })); + if (e.NewValue is InkToolbarToolButton b) + t.ActiveTool = b; + } + })); - #endregion + #endregion - #region InsetClip + #region InsetClip - public static Thickness GetInsetClip(DependencyObject obj) - { - return (Thickness)obj.GetValue(InsetClipProperty); - } + public static Thickness GetInsetClip(DependencyObject obj) + { + return (Thickness)obj.GetValue(InsetClipProperty); + } - public static void SetInsetClip(DependencyObject obj, Thickness value) - { - obj.SetValue(InsetClipProperty, value); - } + public static void SetInsetClip(DependencyObject obj, Thickness value) + { + obj.SetValue(InsetClipProperty, value); + } - public static readonly DependencyProperty InsetClipProperty = - DependencyProperty.RegisterAttached("InsetClip", typeof(Thickness), typeof(Properties), new PropertyMetadata(new Thickness(0d), (d, e) => + public static readonly DependencyProperty InsetClipProperty = + DependencyProperty.RegisterAttached("InsetClip", typeof(Thickness), typeof(Properties), new PropertyMetadata(new Thickness(0d), (d, e) => + { + if (d is FrameworkElement f && e.NewValue is Thickness t) { - if (d is FrameworkElement f && e.NewValue is Thickness t) - { - Visual v = f.GetElementVisual(); - if (t.Left > 0 || t.Right > 0 || t.Top > 0 || t.Bottom > 0) - v.Clip = v.Compositor.CreateInsetClip((float)t.Left, (float)t.Top, (float)t.Right, (float)t.Bottom); - else - v.Clip = null; - } - })); + Visual v = f.GetElementVisual(); + if (t.Left > 0 || t.Right > 0 || t.Top > 0 || t.Bottom > 0) + v.Clip = v.Compositor.CreateInsetClip((float)t.Left, (float)t.Top, (float)t.Right, (float)t.Bottom); + else + v.Clip = null; + } + })); - #endregion + #endregion - #region TabViewItem IsCompact + #region TabViewItem IsCompact - public static bool GetIsCompact(DependencyObject obj) - { - return (bool)obj.GetValue(IsCompactProperty); - } + public static bool GetIsCompact(DependencyObject obj) + { + return (bool)obj.GetValue(IsCompactProperty); + } - public static void SetIsCompact(DependencyObject obj, bool value) - { - obj.SetValue(IsCompactProperty, value); - } + public static void SetIsCompact(DependencyObject obj, bool value) + { + obj.SetValue(IsCompactProperty, value); + } - public static readonly DependencyProperty IsCompactProperty = - DependencyProperty.RegisterAttached("IsCompact", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d, e) => + public static readonly DependencyProperty IsCompactProperty = + DependencyProperty.RegisterAttached("IsCompact", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d, e) => + { + if (d is TabViewItem item && e.NewValue is bool b) { - if (d is TabViewItem item && e.NewValue is bool b) - { - VisualStateManager.GoToState(item, b ? "CollapsedTabState" : "FullTabState", ResourceHelper.AllowAnimation); + VisualStateManager.GoToState(item, b ? "CollapsedTabState" : "FullTabState", ResourceHelper.AllowAnimation); - if (b) - item.MaxWidth = 60; - else - item.ClearValue(TabViewItem.MaxWidthProperty); - } - })); + if (b) + item.MaxWidth = 60; + else + item.ClearValue(TabViewItem.MaxWidthProperty); + } + })); - #endregion + #endregion - #region IsTabOpenAnimationEnabled + #region IsTabOpenAnimationEnabled - public static bool GetIsTabOpenAnimationEnabled(DependencyObject obj) - { - return (bool)obj.GetValue(IsTabOpenAnimationEnabledProperty); - } + public static bool GetIsTabOpenAnimationEnabled(DependencyObject obj) + { + return (bool)obj.GetValue(IsTabOpenAnimationEnabledProperty); + } - public static void SetIsTabOpenAnimationEnabled(DependencyObject obj, bool value) - { - obj.SetValue(IsTabOpenAnimationEnabledProperty, value); - } + public static void SetIsTabOpenAnimationEnabled(DependencyObject obj, bool value) + { + obj.SetValue(IsTabOpenAnimationEnabledProperty, value); + } - public static readonly DependencyProperty IsTabOpenAnimationEnabledProperty = - DependencyProperty.RegisterAttached("IsTabOpenAnimationEnabled", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d, e) => + public static readonly DependencyProperty IsTabOpenAnimationEnabledProperty = + DependencyProperty.RegisterAttached("IsTabOpenAnimationEnabled", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d, e) => + { + if (d is TabView tabs && e.NewValue is bool b) { - if (d is TabView tabs && e.NewValue is bool b) + // We can only configure animation if the TabView template is loaded. + // Check that it is, or wait until it is. + if (tabs.GetFirstDescendantOfType() is ListView list) + ConfigureAnimations(tabs, b); + else { - // We can only configure animation if the TabView template is loaded. - // Check that it is, or wait until it is. - if (tabs.GetFirstDescendantOfType() is ListView list) - ConfigureAnimations(tabs, b); - else - { - tabs.Loaded -= Tabs_Loaded; - tabs.Loaded += Tabs_Loaded; - } + tabs.Loaded -= Tabs_Loaded; + tabs.Loaded += Tabs_Loaded; + } - static void Tabs_Loaded(object sender, RoutedEventArgs e) - { - if (sender is TabView t) - ConfigureAnimations(t, Properties.GetIsTabOpenAnimationEnabled(t)); - } + static void Tabs_Loaded(object sender, RoutedEventArgs e) + { + if (sender is TabView t) + ConfigureAnimations(t, Properties.GetIsTabOpenAnimationEnabled(t)); + } + + + static void ConfigureAnimations(TabView view, bool enabled) + { + view.Loaded -= Tabs_Loaded; + List _tooAdd = new(); - static void ConfigureAnimations(TabView view, bool enabled) + if (view.GetFirstDescendantOfType() is ListView list) { - view.Loaded -= Tabs_Loaded; + list.Items.VectorChanged -= Items_VectorChanged; + list.ContainerContentChanging -= List_ContainerContentChanging; - List _tooAdd = new(); + if (enabled) + { + list.Items.VectorChanged += Items_VectorChanged; + list.ContainerContentChanging += List_ContainerContentChanging; + } - if (view.GetFirstDescendantOfType() is ListView list) + void Items_VectorChanged(IObservableVector sender, IVectorChangedEventArgs eargs) { - list.Items.VectorChanged -= Items_VectorChanged; - list.ContainerContentChanging -= List_ContainerContentChanging; + if (ResourceHelper.AllowAnimation is false) + return; - if (enabled) - { - list.Items.VectorChanged += Items_VectorChanged; - list.ContainerContentChanging += List_ContainerContentChanging; - } + // TabView sometimes decides to give new item containers to existing items already + // in view, and we want to make sure to only animate items that a re new, so make + // a record of them. + if (eargs.CollectionChange == CollectionChange.ItemInserted) + _tooAdd.Add(new WeakReference(sender[(int)eargs.Index])); + } - void Items_VectorChanged(IObservableVector sender, IVectorChangedEventArgs eargs) + void List_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) + { + // Manually trigger the animation every time the tab content changes + if (args.Item is not null + && args.ItemContainer is not null + && ResourceHelper.AllowAnimation + && _tooAdd.FirstOrDefault(r => r.IsAlive && r.Target == args.Item) is WeakReference itemRef) { - if (ResourceHelper.AllowAnimation is false) - return; - - // TabView sometimes decides to give new item containers to existing items already - // in view, and we want to make sure to only animate items that a re new, so make - // a record of them. - if (eargs.CollectionChange == CollectionChange.ItemInserted) - _tooAdd.Add(new WeakReference(sender[(int)eargs.Index])); - } + _tooAdd.Remove(itemRef); - void List_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) - { - // Manually trigger the animation every time the tab content changes - if (args.Item is not null - && args.ItemContainer is not null - && ResourceHelper.AllowAnimation - && _tooAdd.FirstOrDefault(r => r.IsAlive && r.Target == args.Item) is WeakReference itemRef) + // We animate the first child because if we animate the TabItem itself hit-testing + // will break. + var child = args.ItemContainer.GetFirstDescendantOfType(); + Visual v = child.EnableCompositionTranslation().GetElementVisual(); + + // Get the animation from cache + v.StartAnimation(v.GetCached("_tbopai", () => { - _tooAdd.Remove(itemRef); - - // We animate the first child because if we animate the TabItem itself hit-testing - // will break. - var child = args.ItemContainer.GetFirstDescendantOfType(); - Visual v = child.EnableCompositionTranslation().GetElementVisual(); - - // Get the animation from cache - v.StartAnimation(v.GetCached("_tbopai", () => - { - return v.CreateVector3KeyFrameAnimation(CompositionFactory.TRANSLATION) - .SetDelayBehavior(AnimationDelayBehavior.SetInitialValueBeforeDelay) - .SetDelayTime(0.05) - .AddKeyFrame(0, 0, 60) - .AddKeyFrame(1, 0, 0) - .SetDuration(0.325); - })); - } - - // Clean up WeakReferences - foreach (var item in _tooAdd.ToList()) - if (item.IsAlive is false) - _tooAdd.Remove(item); - - // Quick hack to apply GetRequireOpenTab states if only one tab is open by default - if (args.ItemContainer is not null - && sender.Items.Count == 1 - && GetRequireOpenTab(view)) - VisualStateManager.GoToState(args.ItemContainer, "CloseButtonDisabledState", ResourceHelper.AllowAnimation); + return v.CreateVector3KeyFrameAnimation(CompositionFactory.TRANSLATION) + .SetDelayBehavior(AnimationDelayBehavior.SetInitialValueBeforeDelay) + .SetDelayTime(0.05) + .AddKeyFrame(0, 0, 60) + .AddKeyFrame(1, 0, 0) + .SetDuration(0.325); + })); } + + // Clean up WeakReferences + foreach (var item in _tooAdd.ToList()) + if (item.IsAlive is false) + _tooAdd.Remove(item); + + // Quick hack to apply GetRequireOpenTab states if only one tab is open by default + if (args.ItemContainer is not null + && sender.Items.Count == 1 + && GetRequireOpenTab(view)) + VisualStateManager.GoToState(args.ItemContainer, "CloseButtonDisabledState", ResourceHelper.AllowAnimation); } } } - })); + } + })); - #endregion + #endregion - #region RequireOpenTab + #region RequireOpenTab - public static bool GetRequireOpenTab(DependencyObject obj) - { - return (bool)obj.GetValue(RequireOpenTabProperty); - } + public static bool GetRequireOpenTab(DependencyObject obj) + { + return (bool)obj.GetValue(RequireOpenTabProperty); + } + + public static void SetRequireOpenTab(DependencyObject obj, bool value) + { + obj.SetValue(RequireOpenTabProperty, value); + } - public static void SetRequireOpenTab(DependencyObject obj, bool value) + public static readonly DependencyProperty RequireOpenTabProperty = + DependencyProperty.RegisterAttached("RequireOpenTab", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d, e) => { - obj.SetValue(RequireOpenTabProperty, value); - } + if (d is TabView tabs) + { + tabs.TabItemsChanged -= TabItemsChanged; + tabs.TabItemsChanged += TabItemsChanged; + } - public static readonly DependencyProperty RequireOpenTabProperty = - DependencyProperty.RegisterAttached("RequireOpenTab", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d, e) => + static void TabItemsChanged(TabView sender, IVectorChangedEventArgs args) { - if (d is TabView tabs) + var items = sender.GetFirstLevelDescendantsOfType().ToList(); + if (GetRequireOpenTab(sender) && sender.TabItems.Count == 1) { - tabs.TabItemsChanged -= TabItemsChanged; - tabs.TabItemsChanged += TabItemsChanged; + foreach (var item in items) + VisualStateManager.GoToState(item, "CloseButtonDisabledState", ResourceHelper.AllowAnimation); } - - static void TabItemsChanged(TabView sender, IVectorChangedEventArgs args) + else { - var items = sender.GetFirstLevelDescendantsOfType().ToList(); - if (GetRequireOpenTab(sender) && sender.TabItems.Count == 1) - { - foreach (var item in items) - VisualStateManager.GoToState(item, "CloseButtonDisabledState", ResourceHelper.AllowAnimation); - } - else - { - foreach (var item in items) - VisualStateManager.GoToState(item, "CloseButtonEnabledState", ResourceHelper.AllowAnimation); - } + foreach (var item in items) + VisualStateManager.GoToState(item, "CloseButtonEnabledState", ResourceHelper.AllowAnimation); } - })); + } + })); - #endregion + #endregion - #region TargetContextFlyout + #region TargetContextFlyout - public static FlyoutBase GetTargetContextFlyout(DependencyObject obj) - { - return (FlyoutBase)obj.GetValue(TargetContextFlyoutProperty); - } + public static FlyoutBase GetTargetContextFlyout(DependencyObject obj) + { + return (FlyoutBase)obj.GetValue(TargetContextFlyoutProperty); + } - public static void SetTargetContextFlyout(DependencyObject obj, FlyoutBase value) + public static void SetTargetContextFlyout(DependencyObject obj, FlyoutBase value) + { + obj.SetValue(TargetContextFlyoutProperty, value); + } + + public static readonly DependencyProperty TargetContextFlyoutProperty = + DependencyProperty.RegisterAttached("TargetContextFlyout", typeof(FlyoutBase), typeof(Properties), new PropertyMetadata(null, (d, e) => { - obj.SetValue(TargetContextFlyoutProperty, value); - } + if (d is FrameworkElement f) + { + f.ContextRequested -= ContextRequested; + f.ContextRequested += ContextRequested; + } - public static readonly DependencyProperty TargetContextFlyoutProperty = - DependencyProperty.RegisterAttached("TargetContextFlyout", typeof(FlyoutBase), typeof(Properties), new PropertyMetadata(null, (d, e) => + static void ContextRequested(UIElement sender, Windows.UI.Xaml.Input.ContextRequestedEventArgs args) { - if (d is FrameworkElement f) + if (GetTargetContextFlyout(sender) is FlyoutBase f) { - f.ContextRequested -= ContextRequested; - f.ContextRequested += ContextRequested; + args.TryGetPosition(sender, out Windows.Foundation.Point p); + f.ShowAt(sender, new() { Position = p }); } + } + })); - static void ContextRequested(UIElement sender, Windows.UI.Xaml.Input.ContextRequestedEventArgs args) - { - if (GetTargetContextFlyout(sender) is FlyoutBase f) - { - args.TryGetPosition(sender, out Windows.Foundation.Point p); - f.ShowAt(sender, new() { Position = p }); - } - } - })); + #endregion - #endregion + #region UseExpandContractAnimation - #region UseExpandContractAnimation + public static bool GetUseExpandContractAnimation(DependencyObject obj) + { + return (bool)obj.GetValue(UseExpandContractAnimationProperty); + } - public static bool GetUseExpandContractAnimation(DependencyObject obj) - { - return (bool)obj.GetValue(UseExpandContractAnimationProperty); - } + public static void SetUseExpandContractAnimation(DependencyObject obj, bool value) + { + obj.SetValue(UseExpandContractAnimationProperty, value); + } - public static void SetUseExpandContractAnimation(DependencyObject obj, bool value) + public static readonly DependencyProperty UseExpandContractAnimationProperty = + DependencyProperty.RegisterAttached("UseExpandContractAnimation", typeof(bool), typeof(Properties), new PropertyMetadata(null, (d, e) => { - obj.SetValue(UseExpandContractAnimationProperty, value); - } - - public static readonly DependencyProperty UseExpandContractAnimationProperty = - DependencyProperty.RegisterAttached("UseExpandContractAnimation", typeof(bool), typeof(Properties), new PropertyMetadata(null, (d, e) => + if (d is FlyoutBase f) { - if (d is FlyoutBase f) - { - f.Opened -= F_Opened; - f.Opened += F_Opened; + f.Opened -= F_Opened; + f.Opened += F_Opened; - f.Closing -= F_Closing; - f.Closing += F_Closing; + f.Closing -= F_Closing; + f.Closing += F_Closing; - if (e.NewValue is bool b) - f.AreOpenCloseAnimationsEnabled = false; - } + if (e.NewValue is bool b) + f.AreOpenCloseAnimationsEnabled = false; + } - /* - * N.B: Ideally these animations would be implemented using UIElement.SetShowAnimation(...) - * and UIElement.SetHideAnimation(...), however this can cause an infinite loop lockup - * at the kernel level for some reason so we must manually play and control the animations. - */ + /* + * N.B: Ideally these animations would be implemented using UIElement.SetShowAnimation(...) + * and UIElement.SetHideAnimation(...), however this can cause an infinite loop lockup + * at the kernel level for some reason so we must manually play and control the animations. + */ - static void F_Closing(FlyoutBase sender, FlyoutBaseClosingEventArgs args) - { - FrameworkElement p = sender.GetPresenter(); + static void F_Closing(FlyoutBase sender, FlyoutBaseClosingEventArgs args) + { + FrameworkElement p = sender.GetPresenter(); - // 1. Check if we actually need to start an animation - if (p is null - || ResourceHelper.AllowAnimation is false - || (p.Tag is string t && t == "Closed")) - return; + // 1. Check if we actually need to start an animation + if (p is null + || ResourceHelper.AllowAnimation is false + || (p.Tag is string t && t == "Closed")) + return; - args.Cancel = true; + args.Cancel = true; - // 2. If already closing, let the existing animation finish - if (p.Tag is string tg && tg == "Closing") - return; + // 2. If already closing, let the existing animation finish + if (p.Tag is string tg && tg == "Closing") + return; - p.Tag = "Closing"; + p.Tag = "Closing"; - Visual v = p.GetElementVisual(); + Visual v = p.GetElementVisual(); - v.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation, - b => + v.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation, + b => + { + var popClose = v.GetCached("UECPopupContract", () => { - var popClose = v.GetCached("UECPopupContract", () => - { - var ease = v.GetCached("ExitEase", - () => v.Compositor.CreateCubicBezierEasingFunction(0.7f, 0.0f, 1.0f, 0.5f)); + var ease = v.GetCached("ExitEase", + () => v.Compositor.CreateCubicBezierEasingFunction(0.7f, 0.0f, 1.0f, 0.5f)); - var scale = v.CreateVector3KeyFrameAnimation(nameof(Visual.Scale)) - .AddScaleKeyFrame(0, 1) - .AddKeyFrame(1.0f, "Vector3(Min(0.01, 20.0 / this.Target.Size.X), Min(0.01, 20.0 / this.Target.Size.Y), 1.0)", ease) - .SetDuration(0.15); + var scale = v.CreateVector3KeyFrameAnimation(nameof(Visual.Scale)) + .AddScaleKeyFrame(0, 1) + .AddKeyFrame(1.0f, "Vector3(Min(0.01, 20.0 / this.Target.Size.X), Min(0.01, 20.0 / this.Target.Size.Y), 1.0)", ease) + .SetDuration(0.15); - var step = v.Compositor.CreateStepEasingFunction(); - step.IsFinalStepSingleFrame = true; + var step = v.Compositor.CreateStepEasingFunction(); + step.IsFinalStepSingleFrame = true; - var op = v.CreateScalarKeyFrameAnimation(nameof(Visual.Opacity)) - .AddKeyFrame(1, 0, step) - .SetDuration(0.13); + var op = v.CreateScalarKeyFrameAnimation(nameof(Visual.Opacity)) + .AddKeyFrame(1, 0, step) + .SetDuration(0.13); - return v.Compositor.CreateAnimationGroup(scale, op); - }); - - v.StartAnimation(popClose); - }, - b => - { - if ((string)p.Tag == "Closing") - { - p.Tag = "Closed"; - sender.Hide(); - } + return v.Compositor.CreateAnimationGroup(scale, op); }); - } - - static void F_Opened(object sender, object e) - { - if ((sender is FlyoutBase flyout - && flyout.GetPresenter() is FrameworkElement presenter) is false) - return; - - Visual v = presenter.GetElementVisual(); - presenter.Tag = "Opening"; - - // 1. Disable animation if turned off - if (GetUseExpandContractAnimation(flyout) is false - || ResourceHelper.AllowAnimation is false) - { - presenter.SetHideAnimation(null); - v.Scale = new(1f); - v.Opacity = 1; - presenter.Tag = "Open"; - return; - } - // 2. Set scale origin - if (v.Scale.X == 1) // If animation has played previously scale will not be 1 + v.StartAnimation(popClose); + }, + b => { - if (presenter.RenderTransformOrigin.X != 0 || presenter.RenderTransformOrigin.Y != 0) + if ((string)p.Tag == "Closing") { - CompositionFactory.StartCentering( - v, (float)presenter.RenderTransformOrigin.X, (float)presenter.RenderTransformOrigin.Y); + p.Tag = "Closed"; + sender.Hide(); } - } + }); + } - // 3. Create Expand animation and play it - var popOpen = v.GetCached("UECPopupExpand", () => - { - var scale = v.CreateVector3KeyFrameAnimation(nameof(Visual.Scale)) - .AddKeyFrame(0.0f, "Vector3(Min(0.01, 20.0 / this.Target.Size.X), Min(0.01, 20.0 / this.Target.Size.Y), 1.0)") - .AddScaleKeyFrame(1, 1, v.Compositor.GetCachedEntranceEase()) - .SetDuration(0.3); + static void F_Opened(object sender, object e) + { + if ((sender is FlyoutBase flyout + && flyout.GetPresenter() is FrameworkElement presenter) is false) + return; - var op = v.CreateScalarKeyFrameAnimation(nameof(Visual.Opacity)) - .SetDuration(0.1) - .AddKeyFrame(0, 1) - .AddKeyFrame(1, 1); + Visual v = presenter.GetElementVisual(); + presenter.Tag = "Opening"; - return v.Compositor.CreateAnimationGroup(scale, op); - }); + // 1. Disable animation if turned off + if (GetUseExpandContractAnimation(flyout) is false + || ResourceHelper.AllowAnimation is false) + { + presenter.SetHideAnimation(null); + v.Scale = new(1f); + v.Opacity = 1; + presenter.Tag = "Open"; + return; + } - v.StartAnimation(popOpen); + // 2. Set scale origin + if (v.Scale.X == 1) // If animation has played previously scale will not be 1 + { + if (presenter.RenderTransformOrigin.X != 0 || presenter.RenderTransformOrigin.Y != 0) + { + CompositionFactory.StartCentering( + v, (float)presenter.RenderTransformOrigin.X, (float)presenter.RenderTransformOrigin.Y); + } } - })); - #endregion + // 3. Create Expand animation and play it + var popOpen = v.GetCached("UECPopupExpand", () => + { + var scale = v.CreateVector3KeyFrameAnimation(nameof(Visual.Scale)) + .AddKeyFrame(0.0f, "Vector3(Min(0.01, 20.0 / this.Target.Size.X), Min(0.01, 20.0 / this.Target.Size.Y), 1.0)") + .AddScaleKeyFrame(1, 1, v.Compositor.GetCachedEntranceEase()) + .SetDuration(0.3); - #region UseStandardReposition + var op = v.CreateScalarKeyFrameAnimation(nameof(Visual.Opacity)) + .SetDuration(0.1) + .AddKeyFrame(0, 1) + .AddKeyFrame(1, 1); - public static bool GetUseStandardReposition(DependencyObject obj) - { - return (bool)obj.GetValue(UseStandardRepositionProperty); - } + return v.Compositor.CreateAnimationGroup(scale, op); + }); - public static void SetUseStandardReposition(DependencyObject obj, bool value) - { - obj.SetValue(UseStandardRepositionProperty, value); - } + v.StartAnimation(popOpen); + } + })); - public static readonly DependencyProperty UseStandardRepositionProperty = - DependencyProperty.RegisterAttached("UseStandardReposition", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d, e) => - { - if (d is FrameworkElement f && e.NewValue is bool b) - { - if (b) - CompositionFactory.SetStandardReposition(f); - else - CompositionFactory.DisableStandardReposition(f); - } - })); + #endregion - #endregion + #region UseStandardReposition - #region ItemContainerTransitions + public static bool GetUseStandardReposition(DependencyObject obj) + { + return (bool)obj.GetValue(UseStandardRepositionProperty); + } - public static TransitionCollection GetItemContainerTransitions(DependencyObject obj) - { - return (TransitionCollection)obj.GetValue(ItemContainerTransitionsProperty); - } + public static void SetUseStandardReposition(DependencyObject obj, bool value) + { + obj.SetValue(UseStandardRepositionProperty, value); + } - public static void SetItemContainerTransitions(DependencyObject obj, TransitionCollection value) + public static readonly DependencyProperty UseStandardRepositionProperty = + DependencyProperty.RegisterAttached("UseStandardReposition", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d, e) => { - obj.SetValue(ItemContainerTransitionsProperty, value); - } + if (d is FrameworkElement f && e.NewValue is bool b) + { + if (b) + CompositionFactory.SetStandardReposition(f); + else + CompositionFactory.DisableStandardReposition(f); + } + })); - public static readonly DependencyProperty ItemContainerTransitionsProperty = - DependencyProperty.RegisterAttached("ItemContainerTransitions", typeof(TransitionCollection), typeof(Properties), new PropertyMetadata(null)); + #endregion - #endregion + #region ItemContainerTransitions - #region ChildrenTransitions + public static TransitionCollection GetItemContainerTransitions(DependencyObject obj) + { + return (TransitionCollection)obj.GetValue(ItemContainerTransitionsProperty); + } - public static TransitionCollection GetChildrenTransitions(DependencyObject obj) - { - return (TransitionCollection)obj.GetValue(ChildrenTransitionsProperty); - } + public static void SetItemContainerTransitions(DependencyObject obj, TransitionCollection value) + { + obj.SetValue(ItemContainerTransitionsProperty, value); + } - public static void SetChildrenTransitions(DependencyObject obj, TransitionCollection value) - { - obj.SetValue(ChildrenTransitionsProperty, value); - } + public static readonly DependencyProperty ItemContainerTransitionsProperty = + DependencyProperty.RegisterAttached("ItemContainerTransitions", typeof(TransitionCollection), typeof(Properties), new PropertyMetadata(null)); - public static readonly DependencyProperty ChildrenTransitionsProperty = - DependencyProperty.RegisterAttached("ChildrenTransitions", typeof(TransitionCollection), typeof(Properties), new PropertyMetadata(null)); + #endregion - #endregion + #region ChildrenTransitions - #region SupportAnimatedIcon + public static TransitionCollection GetChildrenTransitions(DependencyObject obj) + { + return (TransitionCollection)obj.GetValue(ChildrenTransitionsProperty); + } - public static bool GetSupportAnimatedIcon(DependencyObject obj) - { - return (bool)obj.GetValue(SupportAnimatedIconProperty); - } + public static void SetChildrenTransitions(DependencyObject obj, TransitionCollection value) + { + obj.SetValue(ChildrenTransitionsProperty, value); + } - public static void SetSupportAnimatedIcon(DependencyObject obj, bool value) - { - obj.SetValue(SupportAnimatedIconProperty, value); - } + public static readonly DependencyProperty ChildrenTransitionsProperty = + DependencyProperty.RegisterAttached("ChildrenTransitions", typeof(TransitionCollection), typeof(Properties), new PropertyMetadata(null)); - public static readonly DependencyProperty SupportAnimatedIconProperty = - DependencyProperty.RegisterAttached("SupportAnimatedIcon", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d,a) => - { - if (d is FrameworkElement e) - { - AnimatedIcon.SetState(e, "Normal"); + #endregion - e.RemoveHandler(UIElement.PointerPressedEvent, (PointerEventHandler)PointerPressed); - e.RemoveHandler(UIElement.PointerReleasedEvent, (PointerEventHandler)PointerReleased); + #region SupportAnimatedIcon - e.PointerEntered -= PointerEntered; - e.PointerExited -= PointerReleased; + public static bool GetSupportAnimatedIcon(DependencyObject obj) + { + return (bool)obj.GetValue(SupportAnimatedIconProperty); + } - if (a.NewValue is bool b && b) - { - e.AddHandler(FrameworkElement.PointerPressedEvent, new PointerEventHandler(PointerPressed), true); - e.AddHandler(FrameworkElement.PointerReleasedEvent, new PointerEventHandler(PointerReleased), true); + public static void SetSupportAnimatedIcon(DependencyObject obj, bool value) + { + obj.SetValue(SupportAnimatedIconProperty, value); + } - e.PointerEntered += PointerEntered; - e.PointerExited += PointerReleased; - } - } + public static readonly DependencyProperty SupportAnimatedIconProperty = + DependencyProperty.RegisterAttached("SupportAnimatedIcon", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d,a) => + { + if (d is FrameworkElement e) + { + AnimatedIcon.SetState(e, "Normal"); - static void PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e) - { - if (ResourceHelper.AllowAnimation) - AnimatedIcon.SetState((FrameworkElement)sender, "Pressed"); - } + e.RemoveHandler(UIElement.PointerPressedEvent, (PointerEventHandler)PointerPressed); + e.RemoveHandler(UIElement.PointerReleasedEvent, (PointerEventHandler)PointerReleased); - static void PointerEntered(object sender, PointerRoutedEventArgs e) - { - if (ResourceHelper.AllowAnimation) - AnimatedIcon.SetState((FrameworkElement)sender, "PointerOver"); - } + e.PointerEntered -= PointerEntered; + e.PointerExited -= PointerReleased; - static void PointerReleased(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e) + if (a.NewValue is bool b && b) { - AnimatedIcon.SetState((FrameworkElement)sender, "Normal"); + e.AddHandler(FrameworkElement.PointerPressedEvent, new PointerEventHandler(PointerPressed), true); + e.AddHandler(FrameworkElement.PointerReleasedEvent, new PointerEventHandler(PointerReleased), true); + + e.PointerEntered += PointerEntered; + e.PointerExited += PointerReleased; } - })); + } - #endregion + static void PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e) + { + if (ResourceHelper.AllowAnimation) + AnimatedIcon.SetState((FrameworkElement)sender, "Pressed"); + } - #region AttachedStates + static void PointerEntered(object sender, PointerRoutedEventArgs e) + { + if (ResourceHelper.AllowAnimation) + AnimatedIcon.SetState((FrameworkElement)sender, "PointerOver"); + } - public static bool GetUseAttachedStates(DependencyObject obj) - { - return (bool)obj.GetValue(UseAttachedStatesProperty); - } + static void PointerReleased(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e) + { + AnimatedIcon.SetState((FrameworkElement)sender, "Normal"); + } + })); - public static void SetUseAttachedStates(DependencyObject obj, bool value) - { - obj.SetValue(UseAttachedStatesProperty, value); - } + #endregion - public static readonly DependencyProperty UseAttachedStatesProperty = - DependencyProperty.RegisterAttached("UseAttachedStates", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d, a) => - { - if (d is FrameworkElement e) - { - e.RemoveHandler(UIElement.PointerPressedEvent, (PointerEventHandler)PointerPressed); - e.RemoveHandler(UIElement.PointerReleasedEvent, (PointerEventHandler)PointerReleased); + #region AttachedStates + + public static bool GetUseAttachedStates(DependencyObject obj) + { + return (bool)obj.GetValue(UseAttachedStatesProperty); + } - e.PointerEntered -= PointerEntered; - e.PointerExited -= PointerExited; + public static void SetUseAttachedStates(DependencyObject obj, bool value) + { + obj.SetValue(UseAttachedStatesProperty, value); + } - if (a.NewValue is bool b && b) - { - e.AddHandler(FrameworkElement.PointerPressedEvent, new PointerEventHandler(PointerPressed), true); - e.AddHandler(FrameworkElement.PointerReleasedEvent, new PointerEventHandler(PointerReleased), true); + public static readonly DependencyProperty UseAttachedStatesProperty = + DependencyProperty.RegisterAttached("UseAttachedStates", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d, a) => + { + if (d is FrameworkElement e) + { + e.RemoveHandler(UIElement.PointerPressedEvent, (PointerEventHandler)PointerPressed); + e.RemoveHandler(UIElement.PointerReleasedEvent, (PointerEventHandler)PointerReleased); - e.PointerEntered += PointerEntered; - e.PointerExited += PointerExited; - } + e.PointerEntered -= PointerEntered; + e.PointerExited -= PointerExited; - static bool TryStartState(FrameworkElement e, string key) - { - if (ResourceHelper.Get(e, key) is Storyboard state) - { - state.Begin(); - if (ResourceHelper.AllowAnimation is false) - state.SkipToFill(); - return true; - } + if (a.NewValue is bool b && b) + { + e.AddHandler(FrameworkElement.PointerPressedEvent, new PointerEventHandler(PointerPressed), true); + e.AddHandler(FrameworkElement.PointerReleasedEvent, new PointerEventHandler(PointerReleased), true); - return false; - } + e.PointerEntered += PointerEntered; + e.PointerExited += PointerExited; + } - static void PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs _) + static bool TryStartState(FrameworkElement e, string key) + { + if (ResourceHelper.Get(e, key) is Storyboard state) { - if (sender is FrameworkElement e && Properties.GetUseAttachedStates(e)) - TryStartState(e, "Pressed"); + state.Begin(); + if (ResourceHelper.AllowAnimation is false) + state.SkipToFill(); + return true; } - static void PointerEntered(object sender, PointerRoutedEventArgs _) - { - if (sender is FrameworkElement e && Properties.GetUseAttachedStates(e)) - TryStartState(e, "PointerOver"); - } + return false; + } - static void PointerReleased(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs _) - { - if (sender is FrameworkElement e && Properties.GetUseAttachedStates(e)) - TryStartState(e, "Released"); - } + static void PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs _) + { + if (sender is FrameworkElement e && Properties.GetUseAttachedStates(e)) + TryStartState(e, "Pressed"); + } - static void PointerExited(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs _) - { - if (sender is FrameworkElement e && Properties.GetUseAttachedStates(e)) - TryStartState(e, "Normal"); - } + static void PointerEntered(object sender, PointerRoutedEventArgs _) + { + if (sender is FrameworkElement e && Properties.GetUseAttachedStates(e)) + TryStartState(e, "PointerOver"); } - })); + static void PointerReleased(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs _) + { + if (sender is FrameworkElement e && Properties.GetUseAttachedStates(e)) + TryStartState(e, "Released"); + } + static void PointerExited(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs _) + { + if (sender is FrameworkElement e && Properties.GetUseAttachedStates(e)) + TryStartState(e, "Normal"); + } + } + })); - public static string GetName(DependencyObject obj) - { - return (string)obj.GetValue(NameProperty); - } - public static void SetName(DependencyObject obj, string value) - { - obj.SetValue(NameProperty, value); - } - public static readonly DependencyProperty NameProperty = - DependencyProperty.RegisterAttached("Name", typeof(string), typeof(Properties), new PropertyMetadata(null)); + public static string GetName(DependencyObject obj) + { + return (string)obj.GetValue(NameProperty); + } + public static void SetName(DependencyObject obj, string value) + { + obj.SetValue(NameProperty, value); + } + public static readonly DependencyProperty NameProperty = + DependencyProperty.RegisterAttached("Name", typeof(string), typeof(Properties), new PropertyMetadata(null)); - #endregion - #region UseClickAnimation - public static string GetClickAnimation(DependencyObject obj) - { - return (string)obj.GetValue(ClickAnimationProperty); - } + #endregion - public static void SetClickAnimation(DependencyObject obj, string value) - { - obj.SetValue(ClickAnimationProperty, value); - } + #region UseClickAnimation + + public static string GetClickAnimation(DependencyObject obj) + { + return (string)obj.GetValue(ClickAnimationProperty); + } + + public static void SetClickAnimation(DependencyObject obj, string value) + { + obj.SetValue(ClickAnimationProperty, value); + } - public static readonly DependencyProperty ClickAnimationProperty = - DependencyProperty.RegisterAttached("ClickAnimation", typeof(string), typeof(Properties), new PropertyMetadata(null, (d,e) => + public static readonly DependencyProperty ClickAnimationProperty = + DependencyProperty.RegisterAttached("ClickAnimation", typeof(string), typeof(Properties), new PropertyMetadata(null, (d,e) => + { + if (d is FrameworkElement f) { - if (d is FrameworkElement f) - { - // 1. Remove old handlers - if (f is ButtonBase b) - b.Click -= OnClick; + // 1. Remove old handlers + if (f is ButtonBase b) + b.Click -= OnClick; - f.RemoveHandler(UIElement.PointerReleasedEvent, (PointerEventHandler)Clicky); + f.RemoveHandler(UIElement.PointerReleasedEvent, (PointerEventHandler)Clicky); - // 2. Add new handlers - if (e.NewValue is string s && !string.IsNullOrWhiteSpace(s)) - { - if (f is ButtonBase bu) - bu.Click += OnClick; - else - f.AddHandler(FrameworkElement.PointerReleasedEvent, new PointerEventHandler(Clicky), true); - } + // 2. Add new handlers + if (e.NewValue is string s && !string.IsNullOrWhiteSpace(s)) + { + if (f is ButtonBase bu) + bu.Click += OnClick; + else + f.AddHandler(FrameworkElement.PointerReleasedEvent, new PointerEventHandler(Clicky), true); + } - static void Clicky(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs _) - { - if (sender is FrameworkElement e - && ResourceHelper.AllowAnimation - && ResourceHelper.SupportFluentAnimation - && Properties.GetClickAnimation(e) is string s - && !string.IsNullOrWhiteSpace(s)) - CompButtonAnimate(e, s, GetClickAnimationOffset(e)); - } + static void Clicky(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs _) + { + if (sender is FrameworkElement e + && ResourceHelper.AllowAnimation + && ResourceHelper.SupportFluentAnimation + && Properties.GetClickAnimation(e) is string s + && !string.IsNullOrWhiteSpace(s)) + CompButtonAnimate(e, s, GetClickAnimationOffset(e)); + } - static void OnClick(object sender, RoutedEventArgs _) - { - if (sender is FrameworkElement e - && ResourceHelper.AllowAnimation - && ResourceHelper.SupportFluentAnimation - && Properties.GetClickAnimation(e) is string s - && !string.IsNullOrWhiteSpace(s)) - CompButtonAnimate(e, s, GetClickAnimationOffset(e)); - } + static void OnClick(object sender, RoutedEventArgs _) + { + if (sender is FrameworkElement e + && ResourceHelper.AllowAnimation + && ResourceHelper.SupportFluentAnimation + && Properties.GetClickAnimation(e) is string s + && !string.IsNullOrWhiteSpace(s)) + CompButtonAnimate(e, s, GetClickAnimationOffset(e)); } - })); + } + })); - public static Double GetClickAnimationOffset(DependencyObject obj) - { - return (Double)obj.GetValue(ClickAnimationOffsetProperty); - } + public static Double GetClickAnimationOffset(DependencyObject obj) + { + return (Double)obj.GetValue(ClickAnimationOffsetProperty); + } - public static void SetClickAnimationOffset(DependencyObject obj, Double value) - { - obj.SetValue(ClickAnimationOffsetProperty, value); - } + public static void SetClickAnimationOffset(DependencyObject obj, Double value) + { + obj.SetValue(ClickAnimationOffsetProperty, value); + } - public static readonly DependencyProperty ClickAnimationOffsetProperty = - DependencyProperty.RegisterAttached("ClickAnimationOffset", typeof(Double), typeof(Properties), new PropertyMetadata(-2d)); + public static readonly DependencyProperty ClickAnimationOffsetProperty = + DependencyProperty.RegisterAttached("ClickAnimationOffset", typeof(Double), typeof(Properties), new PropertyMetadata(-2d)); - #endregion + #endregion - #region PointerOverAnimation + #region PointerOverAnimation - static void CompButtonAnimate(FrameworkElement source, string key, double offset, bool over = false) + static void CompButtonAnimate(FrameworkElement source, string key, double offset, bool over = false) + { + var parts = key.Split("|"); + if (source.GetDescendantsOfType() + .FirstOrDefault(fe => fe.Name == parts[0]) is FrameworkElement target) { - var parts = key.Split("|"); - if (source.GetDescendantsOfType() - .FirstOrDefault(fe => fe.Name == parts[0]) is FrameworkElement target) + if (parts.Length > 1 && parts[1] == "Scale") { - if (parts.Length > 1 && parts[1] == "Scale") - { - Visual v = target.GetElementVisual(); - v.StartAnimation(FluentAnimationHelper.CreatePointerUp(v)); - } - else - { - Storyboard sb = new(); - var ease = new BackEase { Amplitude = 0.5, EasingMode = EasingMode.EaseOut }; + Visual v = target.GetElementVisual(); + v.StartAnimation(FluentAnimationHelper.CreatePointerUp(v)); + } + else + { + Storyboard sb = new(); + var ease = new BackEase { Amplitude = 0.5, EasingMode = EasingMode.EaseOut }; - bool hasPressed = string.IsNullOrEmpty(GetPointerPressedAnimation(source)) is false; - double duration = hasPressed ? 0.35 : 0.5; + bool hasPressed = string.IsNullOrEmpty(GetPointerPressedAnimation(source)) is false; + double duration = hasPressed ? 0.35 : 0.5; - // Create translate animation - string path = parts.Length > 1 && parts[1] == "X" - ? TargetProperty.CompositeTransform.TranslateX - : TargetProperty.CompositeTransform.TranslateY; + // Create translate animation + string path = parts.Length > 1 && parts[1] == "X" + ? TargetProperty.CompositeTransform.TranslateX + : TargetProperty.CompositeTransform.TranslateY; - var t = sb.CreateTimeline(target, path); - if (over || hasPressed is false) - t.AddKeyFrame(0.15, offset); + var t = sb.CreateTimeline(target, path); + if (over || hasPressed is false) + t.AddKeyFrame(0.15, offset); - t.AddKeyFrame(duration, 0, ease); + t.AddKeyFrame(duration, 0, ease); - sb.Begin(); - } + sb.Begin(); } } + } - public static string GetPointerOverAnimation(DependencyObject obj) - { - return (string)obj.GetValue(PointerOverAnimationProperty); - } + public static string GetPointerOverAnimation(DependencyObject obj) + { + return (string)obj.GetValue(PointerOverAnimationProperty); + } - public static void SetPointerOverAnimation(DependencyObject obj, string value) - { - obj.SetValue(PointerOverAnimationProperty, value); - } + public static void SetPointerOverAnimation(DependencyObject obj, string value) + { + obj.SetValue(PointerOverAnimationProperty, value); + } - public static readonly DependencyProperty PointerOverAnimationProperty = - DependencyProperty.RegisterAttached("PointerOverAnimation", typeof(string), typeof(Properties), new PropertyMetadata(null, (d, e) => + public static readonly DependencyProperty PointerOverAnimationProperty = + DependencyProperty.RegisterAttached("PointerOverAnimation", typeof(string), typeof(Properties), new PropertyMetadata(null, (d, e) => + { + if (d is FrameworkElement f) { - if (d is FrameworkElement f) - { - // 1. Remove old handlers - f.RemoveHandler(UIElement.PointerEnteredEvent, (PointerEventHandler)PointerOverEntered); + // 1. Remove old handlers + f.RemoveHandler(UIElement.PointerEnteredEvent, (PointerEventHandler)PointerOverEntered); - // 2. Add new handlers - if (e.NewValue is string s && !string.IsNullOrWhiteSpace(s)) - f.AddHandler(FrameworkElement.PointerEnteredEvent, new PointerEventHandler(PointerOverEntered), true); + // 2. Add new handlers + if (e.NewValue is string s && !string.IsNullOrWhiteSpace(s)) + f.AddHandler(FrameworkElement.PointerEnteredEvent, new PointerEventHandler(PointerOverEntered), true); - static void PointerOverEntered(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs _) - { - if (sender is FrameworkElement e - && ResourceHelper.AllowAnimation - && ResourceHelper.SupportFluentAnimation - && ResourceHelper.UsePointerOverAnimations - && Properties.GetPointerOverAnimation(e) is string s - && !string.IsNullOrWhiteSpace(s)) - CompButtonAnimate(e, s, GetPointerAnimationOffset(e), true); - } + static void PointerOverEntered(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs _) + { + if (sender is FrameworkElement e + && ResourceHelper.AllowAnimation + && ResourceHelper.SupportFluentAnimation + && ResourceHelper.UsePointerOverAnimations + && Properties.GetPointerOverAnimation(e) is string s + && !string.IsNullOrWhiteSpace(s)) + CompButtonAnimate(e, s, GetPointerAnimationOffset(e), true); } - })); + } + })); - public static double GetPointerAnimationOffset(DependencyObject obj) - { - return (double)obj.GetValue(PointerAnimationOffsetProperty); - } + public static double GetPointerAnimationOffset(DependencyObject obj) + { + return (double)obj.GetValue(PointerAnimationOffsetProperty); + } - public static void SetPointerAnimationOffset(DependencyObject obj, double value) - { - obj.SetValue(PointerAnimationOffsetProperty, value); - } + public static void SetPointerAnimationOffset(DependencyObject obj, double value) + { + obj.SetValue(PointerAnimationOffsetProperty, value); + } - public static readonly DependencyProperty PointerAnimationOffsetProperty = - DependencyProperty.RegisterAttached("PointerAnimationOffset", typeof(double), typeof(Properties), new PropertyMetadata(-2d)); + public static readonly DependencyProperty PointerAnimationOffsetProperty = + DependencyProperty.RegisterAttached("PointerAnimationOffset", typeof(double), typeof(Properties), new PropertyMetadata(-2d)); - #endregion + #endregion - #region PointerPressedAnimation + #region PointerPressedAnimation - public static string GetPointerPressedAnimation(DependencyObject obj) - { - return (string)obj.GetValue(PointerPressedAnimationProperty); - } + public static string GetPointerPressedAnimation(DependencyObject obj) + { + return (string)obj.GetValue(PointerPressedAnimationProperty); + } - public static void SetPointerPressedAnimation(DependencyObject obj, string value) - { - obj.SetValue(PointerPressedAnimationProperty, value); - } + public static void SetPointerPressedAnimation(DependencyObject obj, string value) + { + obj.SetValue(PointerPressedAnimationProperty, value); + } - public static readonly DependencyProperty PointerPressedAnimationProperty = - DependencyProperty.RegisterAttached("PointerPressedAnimation", typeof(string), typeof(Properties), new PropertyMetadata(null, (d, e) => + public static readonly DependencyProperty PointerPressedAnimationProperty = + DependencyProperty.RegisterAttached("PointerPressedAnimation", typeof(string), typeof(Properties), new PropertyMetadata(null, (d, e) => + { + if (d is FrameworkElement f) { - if (d is FrameworkElement f) - { - // 1. Remove old handlers - f.RemoveHandler(UIElement.PointerPressedEvent, (PointerEventHandler)PointerPressed); - f.RemoveHandler(UIElement.PointerExitedEvent, (PointerEventHandler)PointerExited); + // 1. Remove old handlers + f.RemoveHandler(UIElement.PointerPressedEvent, (PointerEventHandler)PointerPressed); + f.RemoveHandler(UIElement.PointerExitedEvent, (PointerEventHandler)PointerExited); - // 2. Add new handlers - if (e.NewValue is string s && !string.IsNullOrWhiteSpace(s)) - { - f.AddHandler(FrameworkElement.PointerPressedEvent, new PointerEventHandler(PointerPressed), true); - f.AddHandler(FrameworkElement.PointerExitedEvent, new PointerEventHandler(PointerExited), true); - } + // 2. Add new handlers + if (e.NewValue is string s && !string.IsNullOrWhiteSpace(s)) + { + f.AddHandler(FrameworkElement.PointerPressedEvent, new PointerEventHandler(PointerPressed), true); + f.AddHandler(FrameworkElement.PointerExitedEvent, new PointerEventHandler(PointerExited), true); + } - static void PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs _) - { - if (sender is FrameworkElement e - && ResourceHelper.AllowAnimation - && ResourceHelper.SupportFluentAnimation - && Properties.GetPointerPressedAnimation(e) is string s - && !string.IsNullOrWhiteSpace(s)) - DoAnimate(e, s, GetClickAnimationOffset(e)); - } + static void PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs _) + { + if (sender is FrameworkElement e + && ResourceHelper.AllowAnimation + && ResourceHelper.SupportFluentAnimation + && Properties.GetPointerPressedAnimation(e) is string s + && !string.IsNullOrWhiteSpace(s)) + DoAnimate(e, s, GetClickAnimationOffset(e)); + } - static void PointerExited(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs _) - { - if (sender is FrameworkElement e - && ResourceHelper.AllowAnimation - && ResourceHelper.SupportFluentAnimation - && Properties.GetPointerPressedAnimation(e) is string s - && !string.IsNullOrWhiteSpace(s)) - RestoreAnimate(e, s); - } + static void PointerExited(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs _) + { + if (sender is FrameworkElement e + && ResourceHelper.AllowAnimation + && ResourceHelper.SupportFluentAnimation + && Properties.GetPointerPressedAnimation(e) is string s + && !string.IsNullOrWhiteSpace(s)) + RestoreAnimate(e, s); + } - static void RestoreAnimate(FrameworkElement source, string key) + static void RestoreAnimate(FrameworkElement source, string key) + { + var parts = key.Split("|"); + if (source.GetDescendantsOfType() + .FirstOrDefault(fe => fe.Name == parts[0]) is FrameworkElement target) { - var parts = key.Split("|"); - if (source.GetDescendantsOfType() - .FirstOrDefault(fe => fe.Name == parts[0]) is FrameworkElement target) + double duration = 0.35; + + if (parts.Length > 1 && parts[1] == "Scale") { - double duration = 0.35; - - if (parts.Length > 1 && parts[1] == "Scale") - { - Visual v = target.GetElementVisual(); - v.StartAnimation(FluentAnimationHelper.CreatePointerUp(v)); - } - else - { - Storyboard sb = new(); - var ease = new BackEase { Amplitude = 0.5, EasingMode = EasingMode.EaseOut }; - // Create translate animation - var t = sb.CreateTimeline(target, TargetProperty.CompositeTransform.TranslateY) - .AddKeyFrame(duration, 0, ease); - sb.Begin(); + Visual v = target.GetElementVisual(); + v.StartAnimation(FluentAnimationHelper.CreatePointerUp(v)); + } + else + { + Storyboard sb = new(); + var ease = new BackEase { Amplitude = 0.5, EasingMode = EasingMode.EaseOut }; + // Create translate animation + var t = sb.CreateTimeline(target, TargetProperty.CompositeTransform.TranslateY) + .AddKeyFrame(duration, 0, ease); + sb.Begin(); - } } } + } - static void DoAnimate(FrameworkElement source, string key, double offset) + static void DoAnimate(FrameworkElement source, string key, double offset) + { + var parts = key.Split("|"); + if (source.GetDescendantsOfType() + .FirstOrDefault(fe => fe.Name == parts[0]) is FrameworkElement target) { - var parts = key.Split("|"); - if (source.GetDescendantsOfType() - .FirstOrDefault(fe => fe.Name == parts[0]) is FrameworkElement target) + + if (parts.Length > 1 && parts[1] == "Scale") { - - if (parts.Length > 1 && parts[1] == "Scale") - { - Visual v = target.GetElementVisual(); - v.StartAnimation(FluentAnimationHelper.CreatePointerDown(v, (float)offset)); - } - else - { - // Create translate animation - Storyboard sb = new(); - var ease = new BackEase { Amplitude = 0.5, EasingMode = EasingMode.EaseOut }; - sb.CreateTimeline(target, TargetProperty.CompositeTransform.TranslateY) - .AddKeyFrame(0.15, offset) - .AddKeyFrame(0.5, 0, ease); - - sb.Begin(); - } + Visual v = target.GetElementVisual(); + v.StartAnimation(FluentAnimationHelper.CreatePointerDown(v, (float)offset)); + } + else + { + // Create translate animation + Storyboard sb = new(); + var ease = new BackEase { Amplitude = 0.5, EasingMode = EasingMode.EaseOut }; + sb.CreateTimeline(target, TargetProperty.CompositeTransform.TranslateY) + .AddKeyFrame(0.15, offset) + .AddKeyFrame(0.5, 0, ease); + + sb.Begin(); } } } - })); + } + })); - #endregion + #endregion - #region ItemContainerBackgroundTransition + #region ItemContainerBackgroundTransition - private static bool GetSetContainerBackgroundTransition(DependencyObject obj) - { - return (bool)obj.GetValue(SetContainerBackgroundTransitionProperty); - } + private static bool GetSetContainerBackgroundTransition(DependencyObject obj) + { + return (bool)obj.GetValue(SetContainerBackgroundTransitionProperty); + } - private static void SetSetContainerBackgroundTransition(DependencyObject obj, bool value) - { - obj.SetValue(SetContainerBackgroundTransitionProperty, value); - } + private static void SetSetContainerBackgroundTransition(DependencyObject obj, bool value) + { + obj.SetValue(SetContainerBackgroundTransitionProperty, value); + } - public static readonly DependencyProperty SetContainerBackgroundTransitionProperty = - DependencyProperty.RegisterAttached("SetContainerBackgroundTransition", typeof(bool), typeof(Properties), new PropertyMetadata(false)); + public static readonly DependencyProperty SetContainerBackgroundTransitionProperty = + DependencyProperty.RegisterAttached("SetContainerBackgroundTransition", typeof(bool), typeof(Properties), new PropertyMetadata(false)); - public static BrushTransition GetItemContainerBackgroundTransition(DependencyObject obj) - { - return (BrushTransition)obj.GetValue(ItemContainerBackgroundTransitionProperty); - } + public static BrushTransition GetItemContainerBackgroundTransition(DependencyObject obj) + { + return (BrushTransition)obj.GetValue(ItemContainerBackgroundTransitionProperty); + } - public static void SetItemContainerBackgroundTransition(DependencyObject obj, BrushTransition value) - { - obj.SetValue(ItemContainerBackgroundTransitionProperty, value); - } + public static void SetItemContainerBackgroundTransition(DependencyObject obj, BrushTransition value) + { + obj.SetValue(ItemContainerBackgroundTransitionProperty, value); + } - public static readonly DependencyProperty ItemContainerBackgroundTransitionProperty = - DependencyProperty.RegisterAttached("ItemContainerBackgroundTransition", typeof(BrushTransition), typeof(Properties), new PropertyMetadata(null, (d, e) => + public static readonly DependencyProperty ItemContainerBackgroundTransitionProperty = + DependencyProperty.RegisterAttached("ItemContainerBackgroundTransition", typeof(BrushTransition), typeof(Properties), new PropertyMetadata(null, (d, e) => + { + if (d is ListViewBase b) { - if (d is ListViewBase b) - { - b.ContainerContentChanging -= ContainerContentChanging; - b.ContainerContentChanging += ContainerContentChanging; - } + b.ContainerContentChanging -= ContainerContentChanging; + b.ContainerContentChanging += ContainerContentChanging; + } - static void ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) + static void ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) + { + if (args.ItemContainer is not null + && GetSetContainerBackgroundTransition(args.ItemContainer) is false + && args.ItemContainer.ContentTemplateRoot is not null) { - if (args.ItemContainer is not null - && GetSetContainerBackgroundTransition(args.ItemContainer) is false - && args.ItemContainer.ContentTemplateRoot is not null) - { - SetSetContainerBackgroundTransition(args.ItemContainer, true); + SetSetContainerBackgroundTransition(args.ItemContainer, true); - var c = VisualTreeHelper.GetChild(args.ItemContainer, 0); - var b = VisualTreeHelper.GetChild(c, 0); - if (b is Border br && br.BackgroundTransition is null) - br.BackgroundTransition = ResourceHelper.AllowAnimation ? GetItemContainerBackgroundTransition(sender) as BrushTransition : null; - } + var c = VisualTreeHelper.GetChild(args.ItemContainer, 0); + var b = VisualTreeHelper.GetChild(c, 0); + if (b is Border br && br.BackgroundTransition is null) + br.BackgroundTransition = ResourceHelper.AllowAnimation ? GetItemContainerBackgroundTransition(sender) as BrushTransition : null; } - })); + } + })); - #endregion + #endregion - #region Uppercase + #region Uppercase - public static string GetUppercase(DependencyObject obj) - { - return (string)obj.GetValue(UppercaseProperty); - } + public static string GetUppercase(DependencyObject obj) + { + return (string)obj.GetValue(UppercaseProperty); + } + + public static void SetUppercase(DependencyObject obj, string value) + { + obj.SetValue(UppercaseProperty, value); + } - public static void SetUppercase(DependencyObject obj, string value) + public static readonly DependencyProperty UppercaseProperty = + DependencyProperty.RegisterAttached("Uppercase", typeof(string), typeof(Properties), new PropertyMetadata(string.Empty, (d,e) => { - obj.SetValue(UppercaseProperty, value); - } + if (d is TextBlock t && e.NewValue is string s) + t.Text = (s ?? string.Empty).ToUpper(); + })); - public static readonly DependencyProperty UppercaseProperty = - DependencyProperty.RegisterAttached("Uppercase", typeof(string), typeof(Properties), new PropertyMetadata(string.Empty, (d,e) => - { - if (d is TextBlock t && e.NewValue is string s) - t.Text = (s ?? string.Empty).ToUpper(); - })); + #endregion - #endregion + #region DisableTranslation - #region DisableTranslation + public static bool GetDisableTranslation(DependencyObject obj) + { + return (bool)obj.GetValue(DisableTranslationProperty); + } - public static bool GetDisableTranslation(DependencyObject obj) - { - return (bool)obj.GetValue(DisableTranslationProperty); - } + public static void SetDisableTranslation(DependencyObject obj, bool value) + { + obj.SetValue(DisableTranslationProperty, value); + } - public static void SetDisableTranslation(DependencyObject obj, bool value) + public static readonly DependencyProperty DisableTranslationProperty = + DependencyProperty.RegisterAttached("DisableTranslation", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d,e) => { - obj.SetValue(DisableTranslationProperty, value); - } - - public static readonly DependencyProperty DisableTranslationProperty = - DependencyProperty.RegisterAttached("DisableTranslation", typeof(bool), typeof(Properties), new PropertyMetadata(false, (d,e) => + // Fix offset ComboBox's caused by global PerspectiveTransform3D + if (d is Popup popup && e.NewValue is bool s && s) { - // Fix offset ComboBox's caused by global PerspectiveTransform3D - if (d is Popup popup && e.NewValue is bool s && s) + popup.Opened += async (src, arg) => { - popup.Opened += async (src, arg) => - { - //await Task.Delay(32); - if (src is Popup pp && pp.Child?.GetFirstDescendantOfType() is Border b) - b.Translation = new(); - }; - } - })); - #endregion + //await Task.Delay(32); + if (src is Popup pp && pp.Child?.GetFirstDescendantOfType() is Border b) + b.Translation = new(); + }; + } + })); + #endregion - #region Footer + #region Footer - public static object GetFooter(DependencyObject obj) - { - return (object)obj.GetValue(FooterProperty); - } + public static object GetFooter(DependencyObject obj) + { + return (object)obj.GetValue(FooterProperty); + } - public static void SetFooter(DependencyObject obj, object value) - { - obj.SetValue(FooterProperty, value); - } + public static void SetFooter(DependencyObject obj, object value) + { + obj.SetValue(FooterProperty, value); + } - public static readonly DependencyProperty FooterProperty = - DependencyProperty.RegisterAttached("Footer", typeof(object), typeof(Properties), new PropertyMetadata(null)); + public static readonly DependencyProperty FooterProperty = + DependencyProperty.RegisterAttached("Footer", typeof(object), typeof(Properties), new PropertyMetadata(null)); - #endregion + #endregion - #region RenderScale + #region RenderScale - public static double GetRenderScale(DependencyObject obj) - { - return (double)obj.GetValue(RenderScaleProperty); - } + public static double GetRenderScale(DependencyObject obj) + { + return (double)obj.GetValue(RenderScaleProperty); + } - public static void SetRenderScale(DependencyObject obj, double value) - { - obj.SetValue(RenderScaleProperty, value); - } + public static void SetRenderScale(DependencyObject obj, double value) + { + obj.SetValue(RenderScaleProperty, value); + } - // Using a DependencyProperty as the backing store for RenderScale. This enables animation, styling, binding, etc... - public static readonly DependencyProperty RenderScaleProperty = - DependencyProperty.RegisterAttached("RenderScale", typeof(double), typeof(Properties), new PropertyMetadata(1d, (d,e) => + // Using a DependencyProperty as the backing store for RenderScale. This enables animation, styling, binding, etc... + public static readonly DependencyProperty RenderScaleProperty = + DependencyProperty.RegisterAttached("RenderScale", typeof(double), typeof(Properties), new PropertyMetadata(1d, (d,e) => + { + if (d is FrameworkElement f && e.NewValue is double s) { - if (d is FrameworkElement f && e.NewValue is double s) - { - var ct = f.GetCompositeTransform(); - ct.ScaleX = ct.ScaleY = s; - } - })); + var ct = f.GetCompositeTransform(); + ct.ScaleX = ct.ScaleY = s; + } + })); - #endregion + #endregion - #region Flyout + #region Flyout - public static FlyoutBase GetFlyout(DependencyObject obj) - { - return (FlyoutBase)obj.GetValue(FlyoutProperty); - } - - public static void SetFlyout(DependencyObject obj, FlyoutBase value) - { - obj.SetValue(FlyoutProperty, value); - } + public static FlyoutBase GetFlyout(DependencyObject obj) + { + return (FlyoutBase)obj.GetValue(FlyoutProperty); + } - public static readonly DependencyProperty FlyoutProperty = - DependencyProperty.RegisterAttached("Flyout", typeof(FlyoutBase), typeof(Properties), new PropertyMetadata(null, (d,e) => - { - if (d is ButtonBase b) - { - b.Click -= ButtonFlyoutClick; - b.Click += ButtonFlyoutClick; - } - })); + public static void SetFlyout(DependencyObject obj, FlyoutBase value) + { + obj.SetValue(FlyoutProperty, value); + } - private static void ButtonFlyoutClick(object sender, RoutedEventArgs e) + public static readonly DependencyProperty FlyoutProperty = + DependencyProperty.RegisterAttached("Flyout", typeof(FlyoutBase), typeof(Properties), new PropertyMetadata(null, (d,e) => { - if (sender is ButtonBase b && GetFlyout(b) is FlyoutBase fly) + if (d is ButtonBase b) { - fly.ShowAt(b); + b.Click -= ButtonFlyoutClick; + b.Click += ButtonFlyoutClick; } - } + })); - #endregion + private static void ButtonFlyoutClick(object sender, RoutedEventArgs e) + { + if (sender is ButtonBase b && GetFlyout(b) is FlyoutBase fly) + { + fly.ShowAt(b); + } } + + #endregion } diff --git a/CharacterMap/CharacterMap/Core/SVGPathReciever.cs b/CharacterMap/CharacterMap/Core/SVGPathReciever.cs index e6697744..7e3a0d89 100644 --- a/CharacterMap/CharacterMap/Core/SVGPathReciever.cs +++ b/CharacterMap/CharacterMap/Core/SVGPathReciever.cs @@ -1,11 +1,5 @@ using Microsoft.Graphics.Canvas.Geometry; -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; -using System.Numerics; -using System.Text; -using System.Threading.Tasks; namespace CharacterMap.Core { diff --git a/CharacterMap/CharacterMap/Core/TypographyAnalyzer.cs b/CharacterMap/CharacterMap/Core/TypographyAnalyzer.cs index a1950c11..f537835e 100644 --- a/CharacterMap/CharacterMap/Core/TypographyAnalyzer.cs +++ b/CharacterMap/CharacterMap/Core/TypographyAnalyzer.cs @@ -1,89 +1,83 @@ using Microsoft.Graphics.Canvas.Text; -using System.Collections.Generic; -using System.Linq; -using CharacterMapCX; -using CharacterMap.Models; -using System.Globalization; -namespace CharacterMap.Core +namespace CharacterMap.Core; + +public static class TypographyAnalyzer { - public static class TypographyAnalyzer + public static List GetSupportedTypographyFeatures(FontVariant variant) { - public static List GetSupportedTypographyFeatures(FontVariant variant) - { - var features = DirectWrite.GetSupportedTypography(variant.Face).Values.ToList(); - var list = features.Select(f => new TypographyFeatureInfo((CanvasTypographyFeatureName)f)).OrderBy(f => f.DisplayName).ToList(); - return list; - } + var features = DirectWrite.GetSupportedTypography(variant.Face).Values.ToList(); + var list = features.Select(f => new TypographyFeatureInfo((CanvasTypographyFeatureName)f)).OrderBy(f => f.DisplayName).ToList(); + return list; + } - /// - /// Returns a list of Typographic Variants for a character supported by the font. - /// - public static List GetCharacterVariants(FontVariant font, Models.Character character) + /// + /// Returns a list of Typographic Variants for a character supported by the font. + /// + public static List GetCharacterVariants(FontVariant font, Models.Character character) + { + CanvasTextAnalyzer textAnalyzer = new (character.Char, CanvasTextDirection.TopToBottomThenLeftToRight); + KeyValuePair analyzed = textAnalyzer.GetScript().First(); + + List supported = new () { - CanvasTextAnalyzer textAnalyzer = new (character.Char, CanvasTextDirection.TopToBottomThenLeftToRight); - KeyValuePair analyzed = textAnalyzer.GetScript().First(); + TypographyFeatureInfo.None + }; - List supported = new () - { - TypographyFeatureInfo.None - }; + foreach (var feature in font.XamlTypographyFeatures) + { + if (feature == TypographyFeatureInfo.None) + continue; - foreach (var feature in font.XamlTypographyFeatures) - { - if (feature == TypographyFeatureInfo.None) - continue; + var glyphs = textAnalyzer.GetGlyphs(analyzed.Key, font.FontFace, 24, false, false, analyzed.Value); + bool[] results = font.FontFace.GetTypographicFeatureGlyphSupport(analyzed.Value, feature.Feature, glyphs); - var glyphs = textAnalyzer.GetGlyphs(analyzed.Key, font.FontFace, 24, false, false, analyzed.Value); - bool[] results = font.FontFace.GetTypographicFeatureGlyphSupport(analyzed.Value, feature.Feature, glyphs); + if (results.Any(r => r)) + supported.Add(feature); + } - if (results.Any(r => r)) - supported.Add(feature); - } + return supported; + } - return supported; - } + /// + /// Creates a FontAnalysis object for a FontVariant and ensures the custom + /// search map for the font is loaded + /// + /// + /// + public static FontAnalysis Analyze(FontVariant variant, bool loadGlyphNames = true) + { + FontAnalysis analysis = new (variant.Face); + if (loadGlyphNames && analysis.HasGlyphNames) + PrepareSearchMap(variant, analysis.GlyphNameMappings); + return analysis; + } - /// - /// Creates a FontAnalysis object for a FontVariant and ensures the custom - /// search map for the font is loaded - /// - /// - /// - public static FontAnalysis Analyze(FontVariant variant, bool loadGlyphNames = true) - { - FontAnalysis analysis = new (variant.Face); - if (loadGlyphNames && analysis.HasGlyphNames) - PrepareSearchMap(variant, analysis.GlyphNameMappings); - return analysis; - } + public static void PrepareSearchMap(FontVariant variant, FontAnalysis a) + { + if (variant.SearchMap is null && a.HasGlyphNames) + PrepareSearchMap(variant, a.GlyphNameMappings); + } - public static void PrepareSearchMap(FontVariant variant, FontAnalysis a) + private static void PrepareSearchMap(FontVariant variant, IReadOnlyDictionary names) + { + if (variant.SearchMap == null) { - if (variant.SearchMap is null && a.HasGlyphNames) - PrepareSearchMap(variant, a.GlyphNameMappings); - } + uint[] uni = variant.GetGlyphUnicodeIndexes(); + int[] gly = variant.Face.GetGlyphIndices(uni); + IReadOnlyList chars = variant.GetCharacters(); + Dictionary map = new (); - private static void PrepareSearchMap(FontVariant variant, IReadOnlyDictionary names) - { - if (variant.SearchMap == null) + for (int i = 0; i < chars.Count; i++) { - uint[] uni = variant.GetGlyphUnicodeIndexes(); - int[] gly = variant.Face.GetGlyphIndices(uni); - IReadOnlyList chars = variant.GetCharacters(); - Dictionary map = new (); - - for (int i = 0; i < chars.Count; i++) + Character c = chars[i]; + if (names.TryGetValue(gly[i], out string mapping) && !string.IsNullOrEmpty(mapping)) { - Character c = chars[i]; - if (names.TryGetValue(gly[i], out string mapping) && !string.IsNullOrEmpty(mapping)) - { - map.Add(c, mapping); - } + map.Add(c, mapping); } - - variant.SearchMap = map; } + + variant.SearchMap = map; } } } diff --git a/CharacterMap/CharacterMap/Core/TypographyBehavior.cs b/CharacterMap/CharacterMap/Core/TypographyBehavior.cs index cc2a8e50..7724f1c4 100644 --- a/CharacterMap/CharacterMap/Core/TypographyBehavior.cs +++ b/CharacterMap/CharacterMap/Core/TypographyBehavior.cs @@ -1,199 +1,196 @@ using Microsoft.Graphics.Canvas.Text; -using System.Collections.Generic; using Windows.UI.Xaml; using Windows.UI.Xaml.Core.Direct; -namespace CharacterMap.Core +namespace CharacterMap.Core; + +public partial class TypographyBehavior { - public partial class TypographyBehavior + public static void SetTypography(IXamlDirectObject o, CanvasTypographyFeatureName f, XamlDirect _xamlDirect) { - public static void SetTypography(IXamlDirectObject o, CanvasTypographyFeatureName f, XamlDirect _xamlDirect) + /* XAML Direct Helpers. Using XD is faster than setting Dependency Properties */ + void Set(XamlPropertyIndex index, bool value) + { + _xamlDirect.SetBooleanProperty(o, index, value); + } + void SetE(XamlPropertyIndex index, uint e) { - /* XAML Direct Helpers. Using XD is faster than setting Dependency Properties */ - void Set(XamlPropertyIndex index, bool value) - { - _xamlDirect.SetBooleanProperty(o, index, value); - } - void SetE(XamlPropertyIndex index, uint e) - { - _xamlDirect.SetEnumProperty(o, index, e); - } - void SetI(XamlPropertyIndex index, bool val) - { - _xamlDirect.SetInt32Property(o, index, val ? 1 : 0); - } - - /* TODO : ADD EASTASIAN TYPOGRAPY PROPERTIES */ - - /* Set CAPTIAL SPACING */ - /* As Capital Spacing affects character spacing, it has no use when displaying single glyphs */ - //Set(XamlPropertyIndex.Typography_CapitalSpacing, f == CanvasTypographyFeatureName.CapitalSpacing); - - /* Set KERNING */ - /* As Kerning affects character spacing, it has no use when displaying single glyphs */ - //Set(XamlPropertyIndex.Typography_Kerning, f == CanvasTypographyFeatureName.Kerning); - - /* Set SWASHES */ - SetI(XamlPropertyIndex.Typography_StandardSwashes, f == CanvasTypographyFeatureName.Swash); - SetI(XamlPropertyIndex.Typography_ContextualSwashes, f == CanvasTypographyFeatureName.ContextualSwash); - - /* Set ALTERNATES */ - SetI(XamlPropertyIndex.Typography_AnnotationAlternates, f == CanvasTypographyFeatureName.AlternateAnnotationForms); - SetI(XamlPropertyIndex.Typography_StylisticAlternates, f == CanvasTypographyFeatureName.StylisticAlternates); - /* Contextual Alternates applies to combinations of characters, and as such has no purpose here yet */ - Set(XamlPropertyIndex.Typography_ContextualAlternates, f == CanvasTypographyFeatureName.ContextualAlternates); - - /* Set MATHEMATICAL GREEK */ - Set(XamlPropertyIndex.Typography_MathematicalGreek, f == CanvasTypographyFeatureName.MathematicalGreek); - - /* Set FORMS */ - Set(XamlPropertyIndex.Typography_HistoricalForms, f == CanvasTypographyFeatureName.HistoricalForms); - Set(XamlPropertyIndex.Typography_CaseSensitiveForms, f == CanvasTypographyFeatureName.CaseSensitiveForms); - Set(XamlPropertyIndex.Typography_EastAsianExpertForms, f == CanvasTypographyFeatureName.ExpertForms); - - /* Set SLASHED ZERO */ - Set(XamlPropertyIndex.Typography_SlashedZero, f == CanvasTypographyFeatureName.SlashedZero); - - /* Set LIGATURES */ - /* Ligatures only apply to combinations of characters, and as such have no purpose here yet */ - // Set(XamlPropertyIndex.Typography_StandardLigatures, f == CanvasTypographyFeatureName.StandardLigatures); - // Set(XamlPropertyIndex.Typography_ContextualLigatures, f == CanvasTypographyFeatureName.ContextualLigatures); - // Set(XamlPropertyIndex.Typography_HistoricalLigatures, f == CanvasTypographyFeatureName.HistoricalLigatures); - // Set(XamlPropertyIndex.Typography_DiscretionaryLigatures, f == CanvasTypographyFeatureName.DiscretionaryLigatures); - - /* Set CAPITALS */ - if (f == CanvasTypographyFeatureName.SmallCapitals) - SetE(XamlPropertyIndex.Typography_Capitals, (uint)FontCapitals.SmallCaps); - else if (f == CanvasTypographyFeatureName.SmallCapitalsFromCapitals) - SetE(XamlPropertyIndex.Typography_Capitals, (uint)FontCapitals.AllSmallCaps); - else if (f == CanvasTypographyFeatureName.PetiteCapitals) - SetE(XamlPropertyIndex.Typography_Capitals, (uint)FontCapitals.PetiteCaps); - else if (f == CanvasTypographyFeatureName.PetiteCapitalsFromCapitals) - SetE(XamlPropertyIndex.Typography_Capitals, (uint)FontCapitals.AllPetiteCaps); - else if (f == CanvasTypographyFeatureName.Titling) - SetE(XamlPropertyIndex.Typography_Capitals, (uint)FontCapitals.Titling); - else if (f == CanvasTypographyFeatureName.Unicase) - SetE(XamlPropertyIndex.Typography_Capitals, (uint)FontCapitals.Unicase); - else - SetE(XamlPropertyIndex.Typography_Capitals, (uint)FontCapitals.Normal); - - /* Set NUMERAL ALIGNMENT */ - /* Numeral Alignment only apply to combinations of characters, and as such have no purpose here yet */ - //if (f == CanvasTypographyFeatureName.ProportionalFigures) - // SetE(XamlPropertyIndex.Typography_NumeralAlignment, (uint)FontNumeralAlignment.Proportional); - //else if (f == CanvasTypographyFeatureName.TabularFigures) - // SetE(XamlPropertyIndex.Typography_NumeralAlignment, (uint)FontNumeralAlignment.Tabular); - //else - SetE(XamlPropertyIndex.Typography_NumeralAlignment, (uint)FontNumeralAlignment.Normal); - - /* Set NUMERAL STYLE */ - if (f == CanvasTypographyFeatureName.OldStyleFigures) - SetE(XamlPropertyIndex.Typography_NumeralStyle, (uint)FontNumeralStyle.OldStyle); - else if (f == CanvasTypographyFeatureName.LiningFigures) - SetE(XamlPropertyIndex.Typography_NumeralStyle, (uint)FontNumeralStyle.Lining); - else - SetE(XamlPropertyIndex.Typography_NumeralStyle, (uint)FontNumeralStyle.Normal); - - /* Set VARIANTS */ - if (f == CanvasTypographyFeatureName.Ordinals) - SetE(XamlPropertyIndex.Typography_Variants, (uint)FontVariants.Ordinal); - else if (f == CanvasTypographyFeatureName.Superscript) - SetE(XamlPropertyIndex.Typography_Variants, (uint)FontVariants.Superscript); - else if (f == CanvasTypographyFeatureName.Subscript) - SetE(XamlPropertyIndex.Typography_Variants, (uint)FontVariants.Subscript); - else if (f == CanvasTypographyFeatureName.RubyNotationForms) - SetE(XamlPropertyIndex.Typography_Variants, (uint)FontVariants.Ruby); - else if (f == CanvasTypographyFeatureName.ScientificInferiors) - SetE(XamlPropertyIndex.Typography_Variants, (uint)FontVariants.Inferior); - else - SetE(XamlPropertyIndex.Typography_Variants, (uint)FontVariants.Normal); - - - /* Set STLYISTIC SETS */ - Set(XamlPropertyIndex.Typography_StylisticSet1, f == CanvasTypographyFeatureName.StylisticSet1); - Set(XamlPropertyIndex.Typography_StylisticSet2, f == CanvasTypographyFeatureName.StylisticSet2); - Set(XamlPropertyIndex.Typography_StylisticSet3, f == CanvasTypographyFeatureName.StylisticSet3); - Set(XamlPropertyIndex.Typography_StylisticSet4, f == CanvasTypographyFeatureName.StylisticSet4); - Set(XamlPropertyIndex.Typography_StylisticSet5, f == CanvasTypographyFeatureName.StylisticSet5); - Set(XamlPropertyIndex.Typography_StylisticSet6, f == CanvasTypographyFeatureName.StylisticSet6); - Set(XamlPropertyIndex.Typography_StylisticSet7, f == CanvasTypographyFeatureName.StylisticSet7); - Set(XamlPropertyIndex.Typography_StylisticSet8, f == CanvasTypographyFeatureName.StylisticSet8); - Set(XamlPropertyIndex.Typography_StylisticSet9, f == CanvasTypographyFeatureName.StylisticSet9); - Set(XamlPropertyIndex.Typography_StylisticSet10, f == CanvasTypographyFeatureName.StylisticSet10); - Set(XamlPropertyIndex.Typography_StylisticSet11, f == CanvasTypographyFeatureName.StylisticSet11); - Set(XamlPropertyIndex.Typography_StylisticSet12, f == CanvasTypographyFeatureName.StylisticSet12); - Set(XamlPropertyIndex.Typography_StylisticSet13, f == CanvasTypographyFeatureName.StylisticSet13); - Set(XamlPropertyIndex.Typography_StylisticSet14, f == CanvasTypographyFeatureName.StylisticSet14); - Set(XamlPropertyIndex.Typography_StylisticSet15, f == CanvasTypographyFeatureName.StylisticSet15); - Set(XamlPropertyIndex.Typography_StylisticSet16, f == CanvasTypographyFeatureName.StylisticSet16); - Set(XamlPropertyIndex.Typography_StylisticSet17, f == CanvasTypographyFeatureName.StylisticSet17); - Set(XamlPropertyIndex.Typography_StylisticSet18, f == CanvasTypographyFeatureName.StylisticSet18); - Set(XamlPropertyIndex.Typography_StylisticSet19, f == CanvasTypographyFeatureName.StylisticSet19); - Set(XamlPropertyIndex.Typography_StylisticSet20, f == CanvasTypographyFeatureName.StylisticSet20); + _xamlDirect.SetEnumProperty(o, index, e); } + void SetI(XamlPropertyIndex index, bool val) + { + _xamlDirect.SetInt32Property(o, index, val ? 1 : 0); + } + + /* TODO : ADD EASTASIAN TYPOGRAPY PROPERTIES */ + + /* Set CAPTIAL SPACING */ + /* As Capital Spacing affects character spacing, it has no use when displaying single glyphs */ + //Set(XamlPropertyIndex.Typography_CapitalSpacing, f == CanvasTypographyFeatureName.CapitalSpacing); + + /* Set KERNING */ + /* As Kerning affects character spacing, it has no use when displaying single glyphs */ + //Set(XamlPropertyIndex.Typography_Kerning, f == CanvasTypographyFeatureName.Kerning); + + /* Set SWASHES */ + SetI(XamlPropertyIndex.Typography_StandardSwashes, f == CanvasTypographyFeatureName.Swash); + SetI(XamlPropertyIndex.Typography_ContextualSwashes, f == CanvasTypographyFeatureName.ContextualSwash); + + /* Set ALTERNATES */ + SetI(XamlPropertyIndex.Typography_AnnotationAlternates, f == CanvasTypographyFeatureName.AlternateAnnotationForms); + SetI(XamlPropertyIndex.Typography_StylisticAlternates, f == CanvasTypographyFeatureName.StylisticAlternates); + /* Contextual Alternates applies to combinations of characters, and as such has no purpose here yet */ + Set(XamlPropertyIndex.Typography_ContextualAlternates, f == CanvasTypographyFeatureName.ContextualAlternates); + + /* Set MATHEMATICAL GREEK */ + Set(XamlPropertyIndex.Typography_MathematicalGreek, f == CanvasTypographyFeatureName.MathematicalGreek); + + /* Set FORMS */ + Set(XamlPropertyIndex.Typography_HistoricalForms, f == CanvasTypographyFeatureName.HistoricalForms); + Set(XamlPropertyIndex.Typography_CaseSensitiveForms, f == CanvasTypographyFeatureName.CaseSensitiveForms); + Set(XamlPropertyIndex.Typography_EastAsianExpertForms, f == CanvasTypographyFeatureName.ExpertForms); + + /* Set SLASHED ZERO */ + Set(XamlPropertyIndex.Typography_SlashedZero, f == CanvasTypographyFeatureName.SlashedZero); + + /* Set LIGATURES */ + /* Ligatures only apply to combinations of characters, and as such have no purpose here yet */ + // Set(XamlPropertyIndex.Typography_StandardLigatures, f == CanvasTypographyFeatureName.StandardLigatures); + // Set(XamlPropertyIndex.Typography_ContextualLigatures, f == CanvasTypographyFeatureName.ContextualLigatures); + // Set(XamlPropertyIndex.Typography_HistoricalLigatures, f == CanvasTypographyFeatureName.HistoricalLigatures); + // Set(XamlPropertyIndex.Typography_DiscretionaryLigatures, f == CanvasTypographyFeatureName.DiscretionaryLigatures); + + /* Set CAPITALS */ + if (f == CanvasTypographyFeatureName.SmallCapitals) + SetE(XamlPropertyIndex.Typography_Capitals, (uint)FontCapitals.SmallCaps); + else if (f == CanvasTypographyFeatureName.SmallCapitalsFromCapitals) + SetE(XamlPropertyIndex.Typography_Capitals, (uint)FontCapitals.AllSmallCaps); + else if (f == CanvasTypographyFeatureName.PetiteCapitals) + SetE(XamlPropertyIndex.Typography_Capitals, (uint)FontCapitals.PetiteCaps); + else if (f == CanvasTypographyFeatureName.PetiteCapitalsFromCapitals) + SetE(XamlPropertyIndex.Typography_Capitals, (uint)FontCapitals.AllPetiteCaps); + else if (f == CanvasTypographyFeatureName.Titling) + SetE(XamlPropertyIndex.Typography_Capitals, (uint)FontCapitals.Titling); + else if (f == CanvasTypographyFeatureName.Unicase) + SetE(XamlPropertyIndex.Typography_Capitals, (uint)FontCapitals.Unicase); + else + SetE(XamlPropertyIndex.Typography_Capitals, (uint)FontCapitals.Normal); + + /* Set NUMERAL ALIGNMENT */ + /* Numeral Alignment only apply to combinations of characters, and as such have no purpose here yet */ + //if (f == CanvasTypographyFeatureName.ProportionalFigures) + // SetE(XamlPropertyIndex.Typography_NumeralAlignment, (uint)FontNumeralAlignment.Proportional); + //else if (f == CanvasTypographyFeatureName.TabularFigures) + // SetE(XamlPropertyIndex.Typography_NumeralAlignment, (uint)FontNumeralAlignment.Tabular); + //else + SetE(XamlPropertyIndex.Typography_NumeralAlignment, (uint)FontNumeralAlignment.Normal); + + /* Set NUMERAL STYLE */ + if (f == CanvasTypographyFeatureName.OldStyleFigures) + SetE(XamlPropertyIndex.Typography_NumeralStyle, (uint)FontNumeralStyle.OldStyle); + else if (f == CanvasTypographyFeatureName.LiningFigures) + SetE(XamlPropertyIndex.Typography_NumeralStyle, (uint)FontNumeralStyle.Lining); + else + SetE(XamlPropertyIndex.Typography_NumeralStyle, (uint)FontNumeralStyle.Normal); + + /* Set VARIANTS */ + if (f == CanvasTypographyFeatureName.Ordinals) + SetE(XamlPropertyIndex.Typography_Variants, (uint)FontVariants.Ordinal); + else if (f == CanvasTypographyFeatureName.Superscript) + SetE(XamlPropertyIndex.Typography_Variants, (uint)FontVariants.Superscript); + else if (f == CanvasTypographyFeatureName.Subscript) + SetE(XamlPropertyIndex.Typography_Variants, (uint)FontVariants.Subscript); + else if (f == CanvasTypographyFeatureName.RubyNotationForms) + SetE(XamlPropertyIndex.Typography_Variants, (uint)FontVariants.Ruby); + else if (f == CanvasTypographyFeatureName.ScientificInferiors) + SetE(XamlPropertyIndex.Typography_Variants, (uint)FontVariants.Inferior); + else + SetE(XamlPropertyIndex.Typography_Variants, (uint)FontVariants.Normal); + + + /* Set STLYISTIC SETS */ + Set(XamlPropertyIndex.Typography_StylisticSet1, f == CanvasTypographyFeatureName.StylisticSet1); + Set(XamlPropertyIndex.Typography_StylisticSet2, f == CanvasTypographyFeatureName.StylisticSet2); + Set(XamlPropertyIndex.Typography_StylisticSet3, f == CanvasTypographyFeatureName.StylisticSet3); + Set(XamlPropertyIndex.Typography_StylisticSet4, f == CanvasTypographyFeatureName.StylisticSet4); + Set(XamlPropertyIndex.Typography_StylisticSet5, f == CanvasTypographyFeatureName.StylisticSet5); + Set(XamlPropertyIndex.Typography_StylisticSet6, f == CanvasTypographyFeatureName.StylisticSet6); + Set(XamlPropertyIndex.Typography_StylisticSet7, f == CanvasTypographyFeatureName.StylisticSet7); + Set(XamlPropertyIndex.Typography_StylisticSet8, f == CanvasTypographyFeatureName.StylisticSet8); + Set(XamlPropertyIndex.Typography_StylisticSet9, f == CanvasTypographyFeatureName.StylisticSet9); + Set(XamlPropertyIndex.Typography_StylisticSet10, f == CanvasTypographyFeatureName.StylisticSet10); + Set(XamlPropertyIndex.Typography_StylisticSet11, f == CanvasTypographyFeatureName.StylisticSet11); + Set(XamlPropertyIndex.Typography_StylisticSet12, f == CanvasTypographyFeatureName.StylisticSet12); + Set(XamlPropertyIndex.Typography_StylisticSet13, f == CanvasTypographyFeatureName.StylisticSet13); + Set(XamlPropertyIndex.Typography_StylisticSet14, f == CanvasTypographyFeatureName.StylisticSet14); + Set(XamlPropertyIndex.Typography_StylisticSet15, f == CanvasTypographyFeatureName.StylisticSet15); + Set(XamlPropertyIndex.Typography_StylisticSet16, f == CanvasTypographyFeatureName.StylisticSet16); + Set(XamlPropertyIndex.Typography_StylisticSet17, f == CanvasTypographyFeatureName.StylisticSet17); + Set(XamlPropertyIndex.Typography_StylisticSet18, f == CanvasTypographyFeatureName.StylisticSet18); + Set(XamlPropertyIndex.Typography_StylisticSet19, f == CanvasTypographyFeatureName.StylisticSet19); + Set(XamlPropertyIndex.Typography_StylisticSet20, f == CanvasTypographyFeatureName.StylisticSet20); } +} - public partial class TypographyBehavior +public partial class TypographyBehavior +{ + private static HashSet _supportedSingleGlyphFeatures { get; } = new HashSet { - private static HashSet _supportedSingleGlyphFeatures { get; } = new HashSet - { - CanvasTypographyFeatureName.None, - CanvasTypographyFeatureName.StylisticSet1, - CanvasTypographyFeatureName.StylisticSet2, - CanvasTypographyFeatureName.StylisticSet3, - CanvasTypographyFeatureName.StylisticSet4, - CanvasTypographyFeatureName.StylisticSet5, - CanvasTypographyFeatureName.StylisticSet6, - CanvasTypographyFeatureName.StylisticSet7, - CanvasTypographyFeatureName.StylisticSet8, - CanvasTypographyFeatureName.StylisticSet9, - CanvasTypographyFeatureName.StylisticSet10, - CanvasTypographyFeatureName.StylisticSet11, - CanvasTypographyFeatureName.StylisticSet12, - CanvasTypographyFeatureName.StylisticSet13, - CanvasTypographyFeatureName.StylisticSet14, - CanvasTypographyFeatureName.StylisticSet15, - CanvasTypographyFeatureName.StylisticSet16, - CanvasTypographyFeatureName.StylisticSet17, - CanvasTypographyFeatureName.StylisticSet18, - CanvasTypographyFeatureName.StylisticSet19, - CanvasTypographyFeatureName.StylisticSet20, - //CanvasTypographyFeatureName.Kerning, - //CanvasTypographyFeatureName.CapitalSpacing, - CanvasTypographyFeatureName.MathematicalGreek, - CanvasTypographyFeatureName.HistoricalForms, - CanvasTypographyFeatureName.CaseSensitiveForms, - CanvasTypographyFeatureName.ExpertForms, - CanvasTypographyFeatureName.SlashedZero, - //CanvasTypographyFeatureName.ContextualAlternates, - //CanvasTypographyFeatureName.StandardLigatures, - //CanvasTypographyFeatureName.ContextualLigatures, - //CanvasTypographyFeatureName.HistoricalLigatures, - //CanvasTypographyFeatureName.DiscretionaryLigatures, - CanvasTypographyFeatureName.SmallCapitals, - CanvasTypographyFeatureName.SmallCapitalsFromCapitals, - CanvasTypographyFeatureName.PetiteCapitals, - CanvasTypographyFeatureName.PetiteCapitalsFromCapitals, - CanvasTypographyFeatureName.Titling, - CanvasTypographyFeatureName.Unicase, - //CanvasTypographyFeatureName.ProportionalFigures, - //CanvasTypographyFeatureName.TabularFigures, - CanvasTypographyFeatureName.OldStyleFigures, - CanvasTypographyFeatureName.LiningFigures, - CanvasTypographyFeatureName.Ordinals, - CanvasTypographyFeatureName.Superscript, - CanvasTypographyFeatureName.Subscript, - CanvasTypographyFeatureName.RubyNotationForms, - CanvasTypographyFeatureName.ScientificInferiors, - CanvasTypographyFeatureName.Swash, - CanvasTypographyFeatureName.ContextualSwash, - CanvasTypographyFeatureName.AlternateAnnotationForms, - CanvasTypographyFeatureName.StylisticAlternates - }; - - public static bool IsXamlSingleGlyphSupported(CanvasTypographyFeatureName feature) - => _supportedSingleGlyphFeatures.Contains(feature); - } - + CanvasTypographyFeatureName.None, + CanvasTypographyFeatureName.StylisticSet1, + CanvasTypographyFeatureName.StylisticSet2, + CanvasTypographyFeatureName.StylisticSet3, + CanvasTypographyFeatureName.StylisticSet4, + CanvasTypographyFeatureName.StylisticSet5, + CanvasTypographyFeatureName.StylisticSet6, + CanvasTypographyFeatureName.StylisticSet7, + CanvasTypographyFeatureName.StylisticSet8, + CanvasTypographyFeatureName.StylisticSet9, + CanvasTypographyFeatureName.StylisticSet10, + CanvasTypographyFeatureName.StylisticSet11, + CanvasTypographyFeatureName.StylisticSet12, + CanvasTypographyFeatureName.StylisticSet13, + CanvasTypographyFeatureName.StylisticSet14, + CanvasTypographyFeatureName.StylisticSet15, + CanvasTypographyFeatureName.StylisticSet16, + CanvasTypographyFeatureName.StylisticSet17, + CanvasTypographyFeatureName.StylisticSet18, + CanvasTypographyFeatureName.StylisticSet19, + CanvasTypographyFeatureName.StylisticSet20, + //CanvasTypographyFeatureName.Kerning, + //CanvasTypographyFeatureName.CapitalSpacing, + CanvasTypographyFeatureName.MathematicalGreek, + CanvasTypographyFeatureName.HistoricalForms, + CanvasTypographyFeatureName.CaseSensitiveForms, + CanvasTypographyFeatureName.ExpertForms, + CanvasTypographyFeatureName.SlashedZero, + //CanvasTypographyFeatureName.ContextualAlternates, + //CanvasTypographyFeatureName.StandardLigatures, + //CanvasTypographyFeatureName.ContextualLigatures, + //CanvasTypographyFeatureName.HistoricalLigatures, + //CanvasTypographyFeatureName.DiscretionaryLigatures, + CanvasTypographyFeatureName.SmallCapitals, + CanvasTypographyFeatureName.SmallCapitalsFromCapitals, + CanvasTypographyFeatureName.PetiteCapitals, + CanvasTypographyFeatureName.PetiteCapitalsFromCapitals, + CanvasTypographyFeatureName.Titling, + CanvasTypographyFeatureName.Unicase, + //CanvasTypographyFeatureName.ProportionalFigures, + //CanvasTypographyFeatureName.TabularFigures, + CanvasTypographyFeatureName.OldStyleFigures, + CanvasTypographyFeatureName.LiningFigures, + CanvasTypographyFeatureName.Ordinals, + CanvasTypographyFeatureName.Superscript, + CanvasTypographyFeatureName.Subscript, + CanvasTypographyFeatureName.RubyNotationForms, + CanvasTypographyFeatureName.ScientificInferiors, + CanvasTypographyFeatureName.Swash, + CanvasTypographyFeatureName.ContextualSwash, + CanvasTypographyFeatureName.AlternateAnnotationForms, + CanvasTypographyFeatureName.StylisticAlternates + }; + + public static bool IsXamlSingleGlyphSupported(CanvasTypographyFeatureName feature) + => _supportedSingleGlyphFeatures.Contains(feature); } diff --git a/CharacterMap/CharacterMap/Core/TypographyFeatureInfo.cs b/CharacterMap/CharacterMap/Core/TypographyFeatureInfo.cs index c8168e0a..30801835 100644 --- a/CharacterMap/CharacterMap/Core/TypographyFeatureInfo.cs +++ b/CharacterMap/CharacterMap/Core/TypographyFeatureInfo.cs @@ -1,88 +1,83 @@ using Microsoft.Graphics.Canvas.Text; -using System; -using System.Collections.Generic; -using System.Linq; -using CharacterMapCX; -namespace CharacterMap.Core +namespace CharacterMap.Core; + +public class TypographyFeatureInfo : ITypographyInfo, IEquatable { - public class TypographyFeatureInfo : ITypographyInfo, IEquatable - { - private static HashSet _allValues { get; } = new HashSet( - Enum.GetValues(typeof(CanvasTypographyFeatureName)).Cast()); + private static HashSet _allValues { get; } = new HashSet( + Enum.GetValues(typeof(CanvasTypographyFeatureName)).Cast()); - public static TypographyFeatureInfo None { get; } = new TypographyFeatureInfo(CanvasTypographyFeatureName.None, "Default"); + public static TypographyFeatureInfo None { get; } = new TypographyFeatureInfo(CanvasTypographyFeatureName.None, "Default"); - public CanvasTypographyFeatureName Feature { get; } + public CanvasTypographyFeatureName Feature { get; } - public string DisplayName { get; } + public string DisplayName { get; } - public TypographyFeatureInfo(CanvasTypographyFeatureName n, string displayName = null) - { - Feature = n; + public TypographyFeatureInfo(CanvasTypographyFeatureName n, string displayName = null) + { + Feature = n; - if (displayName != null) - { - DisplayName = displayName; - } - else if (IsNamedFeature(Feature)) - { - DisplayName = Feature.Humanise(); - } - else - { - // - // For custom font features, we can produce the OpenType feature tag - // using the feature name. - // - uint id = (uint)(Feature); - DisplayName = DirectWrite.GetTagName(id); - } + if (displayName != null) + { + DisplayName = displayName; } - - public override string ToString() + else if (IsNamedFeature(Feature)) { - return DisplayName; + DisplayName = Feature.Humanise(); } - - bool IsNamedFeature(CanvasTypographyFeatureName name) + else { // - // DWrite and Win2D support a pre-defined list of typographic features. - // However, fonts are free to expose features outside of that list. - // In fact, many built-in fonts have such custom features. - // - // These custom features are also accessible through Win2D, and - // are reported by GetSupportedTypographicFeatureNames. + // For custom font features, we can produce the OpenType feature tag + // using the feature name. // - - return _allValues.Contains(name); + uint id = (uint)(Feature); + DisplayName = DirectWrite.GetTagName(id); } + } - public override bool Equals(object obj) - { - return Equals(obj as TypographyFeatureInfo); - } + public override string ToString() + { + return DisplayName; + } - public bool Equals(TypographyFeatureInfo other) - { - return other != null && - Feature == other.Feature; - } + bool IsNamedFeature(CanvasTypographyFeatureName name) + { + // + // DWrite and Win2D support a pre-defined list of typographic features. + // However, fonts are free to expose features outside of that list. + // In fact, many built-in fonts have such custom features. + // + // These custom features are also accessible through Win2D, and + // are reported by GetSupportedTypographicFeatureNames. + // - public override int GetHashCode() - { - return 1334695525 + Feature.GetHashCode(); - } + return _allValues.Contains(name); + } - public static bool operator ==(TypographyFeatureInfo left, TypographyFeatureInfo right) - { - return EqualityComparer.Default.Equals(left, right); - } + public override bool Equals(object obj) + { + return Equals(obj as TypographyFeatureInfo); + } - public static bool operator !=(TypographyFeatureInfo left, TypographyFeatureInfo right) - { - return !(left == right); - } + public bool Equals(TypographyFeatureInfo other) + { + return other != null && + Feature == other.Feature; + } + + public override int GetHashCode() + { + return 1334695525 + Feature.GetHashCode(); + } + + public static bool operator ==(TypographyFeatureInfo left, TypographyFeatureInfo right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(TypographyFeatureInfo left, TypographyFeatureInfo right) + { + return !(left == right); } } diff --git a/CharacterMap/CharacterMap/Core/Utils.cs b/CharacterMap/CharacterMap/Core/Utils.cs index 7b733f59..d0a397da 100644 --- a/CharacterMap/CharacterMap/Core/Utils.cs +++ b/CharacterMap/CharacterMap/Core/Utils.cs @@ -1,21 +1,11 @@ -using CharacterMap.Models; -using CharacterMap.ViewModels; -using CharacterMapCX; -using Microsoft.Graphics.Canvas; +using Microsoft.Graphics.Canvas; using Microsoft.Graphics.Canvas.Svg; using Microsoft.Graphics.Canvas.Text; -using CommunityToolkit.Mvvm.DependencyInjection; -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Windows.ApplicationModel; using Windows.ApplicationModel.DataTransfer; using Windows.Foundation; using Windows.Foundation.Metadata; -using Windows.Storage; using Windows.UI; using Windows.UI.Text; using Windows.UI.ViewManagement; @@ -23,517 +13,514 @@ using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Controls.Primitives; -using Windows.Storage.Streams; -using System.IO; using Windows.System; using Windows.UI.Core; -namespace CharacterMap.Core -{ - public class Pool where T : new() - { - Queue _pool { get; } = new (); +namespace CharacterMap.Core; - public T Request() - { - if (_pool.TryDequeue(out T item)) - return item; +public class Pool where T : new() +{ + Queue _pool { get; } = new (); - return new(); - } + public T Request() + { + if (_pool.TryDequeue(out T item)) + return item; - public void Return(T value) - { - _pool.Enqueue(value); - } + return new(); } - public static class Utils + public void Return(T value) { - public static CanvasDevice CanvasDevice { get; } = CanvasDevice.GetSharedDevice(); + _pool.Enqueue(value); + } +} - public static NativeInterop GetInterop() => Ioc.Default.GetService(); +public static class Utils +{ + public static CanvasDevice CanvasDevice { get; } = CanvasDevice.GetSharedDevice(); - public static void RunOnDispatcher(this DependencyObject d, Action a) - { - if (d.Dispatcher.HasThreadAccess) - a(); - else - _ = d.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => a()); - } + public static NativeInterop GetInterop() => Ioc.Default.GetService(); - public static void ToggleFullScreenMode() - { - var view = ApplicationView.GetForCurrentView(); - if (view.IsFullScreenMode) - view.ExitFullScreenMode(); - else - view.TryEnterFullScreenMode(); - } + public static void RunOnDispatcher(this DependencyObject d, Action a) + { + if (d.Dispatcher.HasThreadAccess) + a(); + else + _ = d.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => a()); + } - public static bool IsKeyDown(VirtualKey key) - { - var state = CoreWindow.GetForCurrentThread().GetKeyState(key); - return (state & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down; - } + public static void ToggleFullScreenMode() + { + var view = ApplicationView.GetForCurrentView(); + if (view.IsFullScreenMode) + view.ExitFullScreenMode(); + else + view.TryEnterFullScreenMode(); + } - public static Color GetAccentColor() - { - if (ApiInformation.IsTypePresent("Windows.UI.ViewManagement.StatusBar")) - { - return (Color)Application.Current.Resources["SystemAccentColor"]; - } - return new UISettings().GetColorValue(UIColorType.Accent); - } + public static bool IsKeyDown(VirtualKey key) + { + var state = CoreWindow.GetForCurrentThread().GetKeyState(key); + return (state & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down; + } - public static void CopyToClipBoard(string str) + public static Color GetAccentColor() + { + if (ApiInformation.IsTypePresent("Windows.UI.ViewManagement.StatusBar")) { - DataPackage dp = new() { RequestedOperation = DataPackageOperation.Copy }; - dp.SetText(str); - Clipboard.SetContent(dp); + return (Color)Application.Current.Resources["SystemAccentColor"]; } + return new UISettings().GetColorValue(UIColorType.Accent); + } - public static async Task TryCopyToClipboardAsync(CopyToClipboardMessage msg, FontMapViewModel viewModel) - { - string c = msg.RequestedItem.GetClipboardString(); + public static void CopyToClipBoard(string str) + { + DataPackage dp = new() { RequestedOperation = DataPackageOperation.Copy }; + dp.SetText(str); + Clipboard.SetContent(dp); + } - if (msg.DataType == CopyDataType.Text) - { - return await TryCopyToClipboardInternalAsync(msg.RequestedItem.Char, c, viewModel); - } - else if (msg.DataType == CopyDataType.SVG) - { - ExportOptions ops = new(ExportFormat.Svg, msg.Style); - var svg = ExportManager.GetSVG(msg.Style, ops.PreferredColor, viewModel.RenderingOptions, msg.RequestedItem); - return await TryCopyToClipboardInternalAsync(svg, c, viewModel, msg.DataType); - } - else if (msg.DataType == CopyDataType.PNG) - { - ExportOptions ops = new(ExportFormat.Png, msg.Style); - IRandomAccessStream data = await ExportManager.GetGlyphPNGStreamAsync(ops, viewModel.RenderingOptions, msg.RequestedItem); - return await TryCopyToClipboardInternalAsync(null, c, viewModel, msg.DataType, data); - } - else - return false; - } + public static async Task TryCopyToClipboardAsync(CopyToClipboardMessage msg, FontMapViewModel viewModel) + { + string c = msg.RequestedItem.GetClipboardString(); - public static Task TryCopyToClipboardAsync(Character character, FontMapViewModel viewModel) + if (msg.DataType == CopyDataType.Text) { - string c = character.GetClipboardString(); - return TryCopyToClipboardInternalAsync(character.Char, c, viewModel); + return await TryCopyToClipboardInternalAsync(msg.RequestedItem.Char, c, viewModel); } - - public static Task TryCopyToClipboardAsync(string s, FontMapViewModel viewModel) + else if (msg.DataType == CopyDataType.SVG) { - string c = string.Join(string.Empty, s.Select(ch => @$"\u{(uint)ch}?")); - return TryCopyToClipboardInternalAsync(s, c, viewModel); + ExportOptions ops = new(ExportFormat.Svg, msg.Style); + var svg = ExportManager.GetSVG(msg.Style, ops.PreferredColor, viewModel.RenderingOptions, msg.RequestedItem); + return await TryCopyToClipboardInternalAsync(svg, c, viewModel, msg.DataType); } - - public static FontVariant GetDefaultVariant(IList variants) + else if (msg.DataType == CopyDataType.PNG) { - return variants.FirstOrDefault(v => v.DirectWriteProperties.Weight.Weight == FontWeights.Normal.Weight && v.DirectWriteProperties.Style == FontStyle.Normal && v.DirectWriteProperties.Stretch == FontStretch.Normal) - ?? variants.FirstOrDefault(v => v.DirectWriteProperties.Weight.Weight == FontWeights.Normal.Weight && v.DirectWriteProperties.Style == FontStyle.Normal) - ?? variants.FirstOrDefault(v => v.DirectWriteProperties.Weight.Weight == FontWeights.Normal.Weight && v.DirectWriteProperties.Stretch == FontStretch.Normal) - ?? variants.FirstOrDefault(v => v.DirectWriteProperties.Weight.Weight == FontWeights.Normal.Weight) - ?? variants[0]; + ExportOptions ops = new(ExportFormat.Png, msg.Style); + IRandomAccessStream data = await ExportManager.GetGlyphPNGStreamAsync(ops, viewModel.RenderingOptions, msg.RequestedItem); + return await TryCopyToClipboardInternalAsync(null, c, viewModel, msg.DataType, data); } + else + return false; + } - public static async Task TryCopyToClipboardInternalAsync(string rawString, string formattedString, FontMapViewModel viewModel, CopyDataType type = CopyDataType.Text, IRandomAccessStream data = null) - { - // Internal helper method to set clipboard - static void TrySetClipboard(string raw, string formatted, FontMapViewModel v, CopyDataType copyType, IRandomAccessStream stream = null) - { - DataPackage dp = new () { RequestedOperation = DataPackageOperation.Copy }; + public static Task TryCopyToClipboardAsync(Character character, FontMapViewModel viewModel) + { + string c = character.GetClipboardString(); + return TryCopyToClipboardInternalAsync(character.Char, c, viewModel); + } - if (raw != null) - dp.SetText(raw); + public static Task TryCopyToClipboardAsync(string s, FontMapViewModel viewModel) + { + string c = string.Join(string.Empty, s.Select(ch => @$"\u{(uint)ch}?")); + return TryCopyToClipboardInternalAsync(s, c, viewModel); + } - if (copyType == CopyDataType.SVG) - AddSVGToPackage(raw, dp); - else if (copyType == CopyDataType.PNG) - { - dp.SetBitmap(RandomAccessStreamReference.CreateFromStream(stream)); - dp.SetData("image/png", stream); - dp.SetData("PNG", stream); - } - else if (!v.SelectedVariant.IsImported) - { - // We can allow users to also copy the glyph with the font meta-data included, - // so when they paste into a supported program like Microsoft Word or - // Adobe Photoshop the correct font is automatically applied to the paste. - // This can't include any Typographic variations unfortunately. - - var rtf = $@"{{\rtf1\fbidis\ansi\ansicpg1252\deff0\nouicompat\deflang2057{{\fonttbl{{\f0\fnil {v.FontFamily.Source};}}}} " + - $@"{{\colortbl;\red0\green0\blue0; }}\pard\plain\f0 {formatted}}}"; - dp.SetRtf(rtf); - - var longName = v.FontFamily.Source; - if (v.SelectedVariant.FontInformation.FirstOrDefault(i => i.Key == "Full Name") is var p) - { - if (p.Value != longName) - longName = $"{v.FontFamily.Source}, {p.Value}"; - } - dp.SetHtmlFormat($"

{raw}

"); - } + public static FontVariant GetDefaultVariant(IList variants) + { + return variants.FirstOrDefault(v => v.DirectWriteProperties.Weight.Weight == FontWeights.Normal.Weight && v.DirectWriteProperties.Style == FontStyle.Normal && v.DirectWriteProperties.Stretch == FontStretch.Normal) + ?? variants.FirstOrDefault(v => v.DirectWriteProperties.Weight.Weight == FontWeights.Normal.Weight && v.DirectWriteProperties.Style == FontStyle.Normal) + ?? variants.FirstOrDefault(v => v.DirectWriteProperties.Weight.Weight == FontWeights.Normal.Weight && v.DirectWriteProperties.Stretch == FontStretch.Normal) + ?? variants.FirstOrDefault(v => v.DirectWriteProperties.Weight.Weight == FontWeights.Normal.Weight) + ?? variants[0]; + } - Clipboard.SetContent(dp); + public static async Task TryCopyToClipboardInternalAsync(string rawString, string formattedString, FontMapViewModel viewModel, CopyDataType type = CopyDataType.Text, IRandomAccessStream data = null) + { + // Internal helper method to set clipboard + static void TrySetClipboard(string raw, string formatted, FontMapViewModel v, CopyDataType copyType, IRandomAccessStream stream = null) + { + DataPackage dp = new () { RequestedOperation = DataPackageOperation.Copy }; - // Possibly causing crashes. - //Clipboard.Flush(); - } + if (raw != null) + dp.SetText(raw); - // Clipboard can fail when setting. - // We will try 5 times. If that fails, we must - int i = 0; - while (i < 5) + if (copyType == CopyDataType.SVG) + AddSVGToPackage(raw, dp); + else if (copyType == CopyDataType.PNG) { - try - { - TrySetClipboard(rawString, formattedString, viewModel, type, data); - return true; - } - catch (Exception ex) when (i < 4) + dp.SetBitmap(RandomAccessStreamReference.CreateFromStream(stream)); + dp.SetData("image/png", stream); + dp.SetData("PNG", stream); + } + else if (!v.SelectedVariant.IsImported) + { + // We can allow users to also copy the glyph with the font meta-data included, + // so when they paste into a supported program like Microsoft Word or + // Adobe Photoshop the correct font is automatically applied to the paste. + // This can't include any Typographic variations unfortunately. + + var rtf = $@"{{\rtf1\fbidis\ansi\ansicpg1252\deff0\nouicompat\deflang2057{{\fonttbl{{\f0\fnil {v.FontFamily.Source};}}}} " + + $@"{{\colortbl;\red0\green0\blue0; }}\pard\plain\f0 {formatted}}}"; + dp.SetRtf(rtf); + + var longName = v.FontFamily.Source; + if (v.SelectedVariant.FontInformation.FirstOrDefault(i => i.Key == "Full Name") is var p) { - await Task.Delay(150); - i++; + if (p.Value != longName) + longName = $"{v.FontFamily.Source}, {p.Value}"; } + dp.SetHtmlFormat($"

{raw}

"); } - return false; - } + Clipboard.SetContent(dp); - private static void AddSVGToPackage(string raw, DataPackage dp) - { - InMemoryRandomAccessStream svgData = new(); - StreamWriter writer = new(svgData.AsStream(), Encoding.UTF8, 1024, true); - writer.Write(raw); - writer.Flush(); - dp.SetData("image/svg+xml", svgData); + // Possibly causing crashes. + //Clipboard.Flush(); } - public static bool TryParseHexString(string hexNumber, out int hex) + // Clipboard can fail when setting. + // We will try 5 times. If that fails, we must + int i = 0; + while (i < 5) { - hexNumber = hexNumber.Replace("x", string.Empty); - if (int.TryParse(hexNumber, NumberStyles.HexNumber, null, out int result)) + try { - hex = result; + TrySetClipboard(rawString, formattedString, viewModel, type, data); return true; } - hex = 0; - return false; + catch (Exception ex) when (i < 4) + { + await Task.Delay(150); + i++; + } } - private static string AsHex(this Color c) + return false; + } + + private static void AddSVGToPackage(string raw, DataPackage dp) + { + InMemoryRandomAccessStream svgData = new(); + StreamWriter writer = new(svgData.AsStream(), Encoding.UTF8, 1024, true); + writer.Write(raw); + writer.Flush(); + dp.SetData("image/svg+xml", svgData); + } + + public static bool TryParseHexString(string hexNumber, out int hex) + { + hexNumber = hexNumber.Replace("x", string.Empty); + if (int.TryParse(hexNumber, NumberStyles.HexNumber, null, out int result)) { - return $"#{c.R:x2}{c.G:x2}{c.B:x2}"; + hex = result; + return true; } + hex = 0; + return false; + } - /// - /// Returns a string attempting to show only characters a font supports. - /// Unsupported characters are replaced with the Unicode replacement character. - /// - /// - /// - public static string GetSafeString(CanvasFontFace fontFace, string s) + private static string AsHex(this Color c) + { + return $"#{c.R:x2}{c.G:x2}{c.B:x2}"; + } + + /// + /// Returns a string attempting to show only characters a font supports. + /// Unsupported characters are replaced with the Unicode replacement character. + /// + /// + /// + public static string GetSafeString(CanvasFontFace fontFace, string s) + { + /* + * Ideally we actually want to use DirectTextBlock + * instead of TextBlock to get correct display of + * Fallback characters, but there is some bug preventing + * rendering I can't figure out, so this is our hack for + * now. + */ + + string r = string.Empty; + if (s != null && fontFace != null) { - /* - * Ideally we actually want to use DirectTextBlock - * instead of TextBlock to get correct display of - * Fallback characters, but there is some bug preventing - * rendering I can't figure out, so this is our hack for - * now. - */ - - string r = string.Empty; - if (s != null && fontFace != null) + for (int i = 0; i < s.Length; i++) { - for (int i = 0; i < s.Length; i++) + var c = s[i]; + + /* Surrogate pair handling is pain */ + if (char.IsSurrogate(c) + && char.IsSurrogatePair(c, s[i + 1])) { - var c = s[i]; - - /* Surrogate pair handling is pain */ - if (char.IsSurrogate(c) - && char.IsSurrogatePair(c, s[i + 1])) - { - var c1 = s[i + 1]; - int val = char.ConvertToUtf32(c, c1); - if (fontFace.HasCharacter((uint)val)) - r += new string(new char[] { c, c1 }); - else - r += '\uFFFD'; - - i += 1; - } - else if (fontFace.HasCharacter(c)) - r += c; + var c1 = s[i + 1]; + int val = char.ConvertToUtf32(c, c1); + if (fontFace.HasCharacter((uint)val)) + r += new string(new char[] { c, c1 }); else r += '\uFFFD'; - } + i += 1; + } + else if (fontFace.HasCharacter(c)) + r += c; + else + r += '\uFFFD'; } - return r; } - public static FrameworkElement GetPresenter(this FlyoutBase flyout) - { - return flyout switch - { - Flyout f => f.GetPresenter(), - MenuFlyout m => m.GetPresenter(), - _ => null - }; - } + return r; + } - public static FlyoutPresenter GetPresenter(this Flyout flyout) + public static FrameworkElement GetPresenter(this FlyoutBase flyout) + { + return flyout switch { - return flyout.Content?.GetFirstAncestorOfType(); - } + Flyout f => f.GetPresenter(), + MenuFlyout m => m.GetPresenter(), + _ => null + }; + } - public static MenuFlyoutPresenter GetPresenter(this MenuFlyout flyout) - { - if (flyout.Items.Count == 0) - return null; + public static FlyoutPresenter GetPresenter(this Flyout flyout) + { + return flyout.Content?.GetFirstAncestorOfType(); + } - return flyout.Items[0].GetFirstAncestorOfType(); - } + public static MenuFlyoutPresenter GetPresenter(this MenuFlyout flyout) + { + if (flyout.Items.Count == 0) + return null; - public static bool IsAccentColorDark() - { - var uiSettings = new UISettings(); - var c = uiSettings.GetColorValue(UIColorType.Accent); - var isDark = (5 * c.G + 2 * c.R + c.B) <= 8 * 128; - return isDark; - } + return flyout.Items[0].GetFirstAncestorOfType(); + } - public static string GetAppDescription() - { - var package = Package.Current; - var packageId = package.Id; - var version = packageId.Version; - var architecture = Package.Current.Id.Architecture.ToString(); + public static bool IsAccentColorDark() + { + var uiSettings = new UISettings(); + var c = uiSettings.GetColorValue(UIColorType.Accent); + var isDark = (5 * c.G + 2 * c.R + c.B) <= 8 * 128; + return isDark; + } - return $"{package.DisplayName} - {version.Major}.{version.Minor}.{version.Build}.{version.Revision} ({architecture})"; - } + public static string GetAppDescription() + { + var package = Package.Current; + var packageId = package.Id; + var version = packageId.Version; + var architecture = Package.Current.Id.Architecture.ToString(); - public static string GetVariantDescription(DWriteFontFace fontFace) - { - StringBuilder s = new StringBuilder(); - s.Append(GetWeightName(fontFace.Properties.Weight)); + return $"{package.DisplayName} - {version.Major}.{version.Minor}.{version.Build}.{version.Revision} ({architecture})"; + } - if (fontFace.Properties.Style != FontStyle.Normal) - s.AppendFormat(", {0}", fontFace.Properties.Style); + public static string GetVariantDescription(DWriteFontFace fontFace) + { + StringBuilder s = new StringBuilder(); + s.Append(GetWeightName(fontFace.Properties.Weight)); - if (fontFace.Properties.Stretch != FontStretch.Normal) - s.AppendFormat(", {0}", fontFace.Properties.Stretch); + if (fontFace.Properties.Style != FontStyle.Normal) + s.AppendFormat(", {0}", fontFace.Properties.Style); - return s.ToString(); - } + if (fontFace.Properties.Stretch != FontStretch.Normal) + s.AppendFormat(", {0}", fontFace.Properties.Stretch); - public static string Humanise(this Enum e) - { - return Humanise(e.ToString(), true); - } + return s.ToString(); + } - public static FlowDirection GetFlowDirection(TextBox box) + public static string Humanise(this Enum e) + { + return Humanise(e.ToString(), true); + } + + public static FlowDirection GetFlowDirection(TextBox box) + { + if (box.Text is not null && box.Text.Length > 0) { - if (box.Text is not null && box.Text.Length > 0) + try { - try - { - // We detect LTR or RTL by checking where the first character is - var rect = box.GetRectFromCharacterIndex(0, false); + // We detect LTR or RTL by checking where the first character is + var rect = box.GetRectFromCharacterIndex(0, false); - // Note: Probably we can check if rect.X == 0, but I haven't properly tested it. - // The current logic will fail with a small width TextBox with small fonts, - // but that's not a concern for our current use cases. - return rect.X <= box.ActualWidth / 4.0 ? FlowDirection.LeftToRight : FlowDirection.RightToLeft; - } - catch - { - // Don't care - } + // Note: Probably we can check if rect.X == 0, but I haven't properly tested it. + // The current logic will fail with a small width TextBox with small fonts, + // but that's not a concern for our current use cases. + return rect.X <= box.ActualWidth / 4.0 ? FlowDirection.LeftToRight : FlowDirection.RightToLeft; + } + catch + { + // Don't care } - - return FlowDirection.LeftToRight; } - static Pool _builderPool { get; } = new Pool(); + return FlowDirection.LeftToRight; + } - /// - /// Not thread safe. - /// - public static string Humanise(string input, bool title) - { - var sb = _builderPool.Request(); + static Pool _builderPool { get; } = new Pool(); - try + /// + /// Not thread safe. + /// + public static string Humanise(string input, bool title) + { + var sb = _builderPool.Request(); + + try + { + char prev = char.MinValue; + for (int i = 0; i < input.Length; i++) { - char prev = char.MinValue; - for (int i = 0; i < input.Length; i++) + char c = input[i]; + + if ((char.IsLower(prev) && char.IsUpper(c)) + || ((char.IsPunctuation(c) || char.IsSeparator(c)) && c != ')') + || (sb.Length > 0 && char.IsDigit(c) && !char.IsDigit(prev)) + || (char.IsDigit(prev) && char.IsLetter(c))) { - char c = input[i]; - - if ((char.IsLower(prev) && char.IsUpper(c)) - || ((char.IsPunctuation(c) || char.IsSeparator(c)) && c != ')') - || (sb.Length > 0 && char.IsDigit(c) && !char.IsDigit(prev)) - || (char.IsDigit(prev) && char.IsLetter(c))) - { - if (c != 32) - sb.Append(' '); - - if (!title) - c = char.ToLowerInvariant(c); - } - - sb.Append(c); - prev = c; + if (c != 32) + sb.Append(' '); + + if (!title) + c = char.ToLowerInvariant(c); } - return sb.ToString(); + sb.Append(c); + prev = c; } - finally - { - sb.Clear(); - _builderPool.Return(sb); - } - } - public static string GetWeightName(FontWeight weight) - { - return weight.Weight switch - { - 100 => nameof(FontWeights.Thin), - 150 => "SemiThin", - 200 => nameof(FontWeights.ExtraLight), - 250 => "SemiExtraLight", - 300 => nameof(FontWeights.Light), - 350 => nameof(FontWeights.SemiLight), - 400 => nameof(FontWeights.Normal), - 450 => "SemiMedium", - 500 => nameof(FontWeights.Medium), - 550 => "ExtraMedium", - 600 => nameof(FontWeights.SemiBold), - 650 => "ExtraSemiBold", - 700 => nameof(FontWeights.Bold), - 750 => "SemiExtraBold", - 800 => nameof(FontWeights.ExtraBold), - 850 => "SemiBlack", - 900 => nameof(FontWeights.Black), - 950 => nameof(FontWeights.ExtraBlack), - _ => weight.Weight.ToString(), - }; + return sb.ToString(); } - - public static CanvasSvgDocument GenerateSvgDocument( - ICanvasResourceCreator device, - Rect rect, - string path, - Color color) + finally { - return GenerateSvgDocument(device, rect, new List { path }, new List { color }); + sb.Clear(); + _builderPool.Return(sb); } + } - /// - /// Generates an SVG document for multi-layered glyphs, where each layer has separate colours. - /// COLR glyphs are an example of this. - /// - /// - /// Bounding rectangle of all glyphs - /// Geometry of each layer - /// Colour of each layer - /// - /// - public static CanvasSvgDocument GenerateSvgDocument( - ICanvasResourceCreator device, - Rect rect, - IList paths, - IList colors, - bool invertBounds = true) + public static string GetWeightName(FontWeight weight) + { + return weight.Weight switch { - var right = Math.Ceiling(rect.Width); - var bottom = Math.Ceiling(rect.Height); - StringBuilder sb = _builderPool.Request(); - - try - { - sb.AppendFormat( - CultureInfo.InvariantCulture, - "", - right, - bottom, - invertBounds ? -Math.Floor(rect.Left) : Math.Floor(rect.Left), - invertBounds ? -Math.Floor(rect.Top) : Math.Floor(rect.Top)); - - foreach (var path in paths) - { - string p = path; - if (path.StartsWith("F1 ")) - p = path.Remove(0, 3); + 100 => nameof(FontWeights.Thin), + 150 => "SemiThin", + 200 => nameof(FontWeights.ExtraLight), + 250 => "SemiExtraLight", + 300 => nameof(FontWeights.Light), + 350 => nameof(FontWeights.SemiLight), + 400 => nameof(FontWeights.Normal), + 450 => "SemiMedium", + 500 => nameof(FontWeights.Medium), + 550 => "ExtraMedium", + 600 => nameof(FontWeights.SemiBold), + 650 => "ExtraSemiBold", + 700 => nameof(FontWeights.Bold), + 750 => "SemiExtraBold", + 800 => nameof(FontWeights.ExtraBold), + 850 => "SemiBlack", + 900 => nameof(FontWeights.Black), + 950 => nameof(FontWeights.ExtraBlack), + _ => weight.Weight.ToString(), + }; + } - if (string.IsNullOrWhiteSpace(p)) - continue; + public static CanvasSvgDocument GenerateSvgDocument( + ICanvasResourceCreator device, + Rect rect, + string path, + Color color) + { + return GenerateSvgDocument(device, rect, new List { path }, new List { color }); + } - sb.AppendFormat("", - p, - colors[paths.IndexOf(path)].AsHex(), - (double)colors[paths.IndexOf(path)].A / 255d); - } - sb.Append(""); + /// + /// Generates an SVG document for multi-layered glyphs, where each layer has separate colours. + /// COLR glyphs are an example of this. + /// + /// + /// Bounding rectangle of all glyphs + /// Geometry of each layer + /// Colour of each layer + /// + /// + public static CanvasSvgDocument GenerateSvgDocument( + ICanvasResourceCreator device, + Rect rect, + IList paths, + IList colors, + bool invertBounds = true) + { + var right = Math.Ceiling(rect.Width); + var bottom = Math.Ceiling(rect.Height); + StringBuilder sb = _builderPool.Request(); - CanvasSvgDocument doc = CanvasSvgDocument.LoadFromXml(device, sb.ToString()); - return doc; - } - finally + try + { + sb.AppendFormat( + CultureInfo.InvariantCulture, + "", + right, + bottom, + invertBounds ? -Math.Floor(rect.Left) : Math.Floor(rect.Left), + invertBounds ? -Math.Floor(rect.Top) : Math.Floor(rect.Top)); + + foreach (var path in paths) { - sb.Clear(); - _builderPool.Return(sb); + string p = path; + if (path.StartsWith("F1 ")) + p = path.Remove(0, 3); + + if (string.IsNullOrWhiteSpace(p)) + continue; + + sb.AppendFormat("", + p, + colors[paths.IndexOf(path)].AsHex(), + (double)colors[paths.IndexOf(path)].A / 255d); } - } + sb.Append(""); - public static Task WriteSvgAsync(CanvasSvgDocument document, IStorageFile file) + CanvasSvgDocument doc = CanvasSvgDocument.LoadFromXml(device, sb.ToString()); + return doc; + } + finally { - return WriteSvgAsync(document.GetXml(), file); + sb.Clear(); + _builderPool.Return(sb); } + } + + public static Task WriteSvgAsync(CanvasSvgDocument document, IStorageFile file) + { + return WriteSvgAsync(document.GetXml(), file); + } + + public static Task WriteSvgAsync(string xml, IStorageFile file) + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine(""); + sb.Append(xml); + return FileIO.WriteTextAsync(file, sb.ToString()).AsTask(); + } - public static Task WriteSvgAsync(string xml, IStorageFile file) + public static async Task DeleteAsync(this StorageFolder folder, bool deleteFolder = false) + { + // 1. Delete all child folders + var folders = await folder.GetFoldersAsync().AsTask().ConfigureAwait(false); + if (folders.Count > 0) { - StringBuilder sb = new StringBuilder(); - sb.AppendLine(""); - sb.Append(xml); - return FileIO.WriteTextAsync(file, sb.ToString()).AsTask(); + var tasks = folders.Select(f => DeleteAsync(f, true)); + await Task.WhenAll(tasks).ConfigureAwait(false); } - public static async Task DeleteAsync(this StorageFolder folder, bool deleteFolder = false) + // 2. Delete child files + var files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false); + if (files.Count > 0) { - // 1. Delete all child folders - var folders = await folder.GetFoldersAsync().AsTask().ConfigureAwait(false); - if (folders.Count > 0) - { - var tasks = folders.Select(f => DeleteAsync(f, true)); - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - // 2. Delete child files - var files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false); - if (files.Count > 0) - { - var tasks = files.Select(f => f.DeleteAsync(StorageDeleteOption.PermanentDelete).AsTask()); - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - // 3. Delete folder - if (deleteFolder) - await folder.DeleteAsync(StorageDeleteOption.PermanentDelete).AsTask().ConfigureAwait(false); + var tasks = files.Select(f => f.DeleteAsync(StorageDeleteOption.PermanentDelete).AsTask()); + await Task.WhenAll(tasks).ConfigureAwait(false); } - public static bool Supports1809 { get; } = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 7); + // 3. Delete folder + if (deleteFolder) + await folder.DeleteAsync(StorageDeleteOption.PermanentDelete).AsTask().ConfigureAwait(false); + } - public static bool Supports1903 { get; } = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8); + public static bool Supports1809 { get; } = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 7); - /// - /// 23H2 Windows 11 builds introduce COLRv1 font format support to DirectWrite - /// - public static bool Supports23H2 { get; } = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 17); - } + public static bool Supports1903 { get; } = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8); + + /// + /// 23H2 Windows 11 builds introduce COLRv1 font format support to DirectWrite + /// + public static bool Supports23H2 { get; } = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 17); } diff --git a/CharacterMap/CharacterMap/GlobalSuppressions.cs b/CharacterMap/CharacterMap/GlobalSuppressions.cs index de0e3d11..5e799eea 100644 --- a/CharacterMap/CharacterMap/GlobalSuppressions.cs +++ b/CharacterMap/CharacterMap/GlobalSuppressions.cs @@ -10,18 +10,24 @@ global using CharacterMap.Services; global using CharacterMap.ViewModels; global using CharacterMapCX; +global using CommunityToolkit.Mvvm.ComponentModel; +global using CommunityToolkit.Mvvm.DependencyInjection; global using CommunityToolkit.Mvvm.Messaging; global using System; global using System.Collections.Generic; global using System.IO; global using System.Linq; global using System.Numerics; +global using System.Runtime.InteropServices.WindowsRuntime; global using System.Runtime.CompilerServices; global using System.Text; global using System.Threading; global using System.Threading.Tasks; +global using Windows.Storage; +global using Windows.Storage.Streams; using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Naming", "VSSpell001:Spell Check", Justification = "BritishEnglish", Scope = "member", Target = "~P:CharacterMap.Core.FontVariant.SupportsColourRendering")] [assembly: SuppressMessage("Naming", "VSSpell001:Spell Check", Justification = "Correct", Scope = "member", Target = "~P:CharacterMap.Core.FontVariant.Panose")] [assembly: SuppressMessage("Naming", "VSSpell001:Spell Check", Justification = "", Scope = "member", Target = "~P:CharacterMap.ViewModels.SettingsViewModel.Changelog")] +[assembly: SuppressMessage("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "MVVMTK0034:Direct field reference to [ObservableProperty] backing field", Justification = "")] diff --git a/CharacterMap/CharacterMap/Helpers/Animation.cs b/CharacterMap/CharacterMap/Helpers/Animation.cs index e62f11ef..e2606060 100644 --- a/CharacterMap/CharacterMap/Helpers/Animation.cs +++ b/CharacterMap/CharacterMap/Helpers/Animation.cs @@ -1,7 +1,4 @@ -using System; -using System.Numerics; -using System.Threading.Tasks; -using Windows.Foundation; +using Windows.Foundation; using Windows.UI; using Windows.UI.Xaml; using Windows.UI.Xaml.Data; diff --git a/CharacterMap/CharacterMap/Helpers/ConcurrencyToken.cs b/CharacterMap/CharacterMap/Helpers/ConcurrencyToken.cs index 5f57151b..e15eb6a1 100644 --- a/CharacterMap/CharacterMap/Helpers/ConcurrencyToken.cs +++ b/CharacterMap/CharacterMap/Helpers/ConcurrencyToken.cs @@ -1,44 +1,41 @@ -using System; +namespace CharacterMap.Helpers; -namespace CharacterMap.Helpers +public class ConcurrencyToken { - public class ConcurrencyToken + public class ConcurrencyTokenGenerator { - public class ConcurrencyTokenGenerator - { - private object Root { get; } = new object(); - private Guid CurrentId { get; set; } = Guid.NewGuid(); + private object Root { get; } = new object(); + private Guid CurrentId { get; set; } = Guid.NewGuid(); - public ConcurrencyToken GenerateToken() + public ConcurrencyToken GenerateToken() + { + lock (Root) { - lock (Root) - { - CurrentId = new Guid(); - return new ConcurrencyToken(CurrentId, this); - } + CurrentId = new Guid(); + return new ConcurrencyToken(CurrentId, this); } + } - internal bool IsValid(ConcurrencyToken token) + internal bool IsValid(ConcurrencyToken token) + { + lock (Root) { - lock (Root) - { - return token.Id == CurrentId; - } + return token.Id == CurrentId; } } + } - private Guid Id { get; } - private ConcurrencyTokenGenerator Owner { get; } + private Guid Id { get; } + private ConcurrencyTokenGenerator Owner { get; } - private ConcurrencyToken(Guid id, ConcurrencyTokenGenerator owner) - { - Id = id; - Owner = owner; - } + private ConcurrencyToken(Guid id, ConcurrencyTokenGenerator owner) + { + Id = id; + Owner = owner; + } - public bool IsValid() - { - return Owner.IsValid(this); - } + public bool IsValid() + { + return Owner.IsValid(this); } } diff --git a/CharacterMap/CharacterMap/Helpers/Debouncer.cs b/CharacterMap/CharacterMap/Helpers/Debouncer.cs index f1136f7b..a0880620 100644 --- a/CharacterMap/CharacterMap/Helpers/Debouncer.cs +++ b/CharacterMap/CharacterMap/Helpers/Debouncer.cs @@ -1,50 +1,44 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Windows.UI.Xaml; - -namespace CharacterMap.Helpers +using Windows.UI.Xaml; + +namespace CharacterMap.Helpers; + +public class Debouncer { - public class Debouncer + private DispatcherTimer _timer = null; + + public bool IsActive => _timer != null && _timer.IsEnabled; + + public void Debounce(int milliseconds, Action action) { - private DispatcherTimer _timer = null; + _timer?.Stop(); + _timer = null; - public bool IsActive => _timer != null && _timer.IsEnabled; + _timer = new DispatcherTimer + { + Interval = TimeSpan.FromMilliseconds(milliseconds) + }; - public void Debounce(int milliseconds, Action action) + _timer.Tick += (s, e) => { + if (_timer == null) + return; + _timer?.Stop(); _timer = null; + action(); + }; - _timer = new DispatcherTimer - { - Interval = TimeSpan.FromMilliseconds(milliseconds) - }; - - _timer.Tick += (s, e) => - { - if (_timer == null) - return; - _timer?.Stop(); - _timer = null; - action(); - }; - - - _timer.Start(); - } + _timer.Start(); + } - public void Cancel() + public void Cancel() + { + if (_timer is not null && _timer.IsEnabled) { - if (_timer is not null && _timer.IsEnabled) - { - _timer?.Stop(); - _timer = null; - } + _timer?.Stop(); + _timer = null; } } } diff --git a/CharacterMap/CharacterMap/Helpers/Extensions.cs b/CharacterMap/CharacterMap/Helpers/Extensions.cs index 768aeb02..e0074203 100644 --- a/CharacterMap/CharacterMap/Helpers/Extensions.cs +++ b/CharacterMap/CharacterMap/Helpers/Extensions.cs @@ -1,171 +1,162 @@ -using CharacterMapCX; -using System; -using System.Linq; -using System.Collections.Generic; -using System.Threading.Tasks; -using Windows.System; +using Windows.System; using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Input; using System.IO.Compression; -using Windows.Storage; -using System.IO; using Microsoft.UI.Xaml.Controls; -using CharacterMap.Models; -namespace CharacterMap.Helpers +namespace CharacterMap.Helpers; + +public static class Extensions { - public static class Extensions - { - /// - /// Creates a copy if the axis list with "new" instances of each axis with matching values. - /// - /// - /// - public static IReadOnlyList Copy(this IEnumerable axis) - { - return axis.Select(a => a.WithValue(a.Value)).ToList(); - } + /// + /// Creates a copy if the axis list with "new" instances of each axis with matching values. + /// + /// + /// + public static IReadOnlyList Copy(this IEnumerable axis) + { + return axis.Select(a => a.WithValue(a.Value)).ToList(); + } - public static IReadOnlyList CopyDefaults(this IEnumerable axis) - { - return axis.Select(a => a.WithValue(a.AxisDefault)).ToList(); - } + public static IReadOnlyList CopyDefaults(this IEnumerable axis) + { + return axis.Select(a => a.WithValue(a.AxisDefault)).ToList(); + } - public static List> Split(this IReadOnlyCollection source, int length) - { - return Enumerable - .Range(0, (source.Count + length - 1) / length) - .Select(n => source.Skip(n * length).Take(length).ToList()) - .ToList(); - } + public static List> Split(this IReadOnlyCollection source, int length) + { + return Enumerable + .Range(0, (source.Count + length - 1) / length) + .Select(n => source.Skip(n * length).Take(length).ToList()) + .ToList(); + } - public static void AddSorted(this IList list, T item, IComparer comparer = null) - { - if (comparer == null) - comparer = Comparer.Default; + public static void AddSorted(this IList list, T item, IComparer comparer = null) + { + if (comparer == null) + comparer = Comparer.Default; - int i = 0; - while (i < list.Count && comparer.Compare(list[i], item) < 0) - i++; + int i = 0; + while (i < list.Count && comparer.Compare(list[i], item) < 0) + i++; - list.Insert(i, item); - } + list.Insert(i, item); + } - public static Task ExecuteAsync(this CoreDispatcher d, Func action, CoreDispatcherPriority p = CoreDispatcherPriority.Normal) - { - TaskCompletionSource tcs = new (); + public static Task ExecuteAsync(this CoreDispatcher d, Func action, CoreDispatcherPriority p = CoreDispatcherPriority.Normal) + { + TaskCompletionSource tcs = new (); - _ =d.RunAsync(p, async () => - { - try - { - await action(); - tcs.SetResult(true); - } - catch (Exception e) - { - tcs.SetException(e); - } - }); - - return tcs.Task; - } - - public static Task ExecuteAsync(this CoreDispatcher d, Action action, CoreDispatcherPriority p = CoreDispatcherPriority.Normal) + _ =d.RunAsync(p, async () => { - TaskCompletionSource tcs = new (); - - _ = d.RunAsync(p, () => + try { - try - { - action(); - tcs.SetResult(true); - } - catch (Exception e) - { - tcs.SetException(e); - } - }); - - return tcs.Task; - } - - public static MenuFlyout AddSeparator(this MenuFlyout menu, bool isVisible = true) - { - menu.Items.Add(new MenuFlyoutSeparator().SetVisible(isVisible)); - return menu; - } + await action(); + tcs.SetResult(true); + } + catch (Exception e) + { + tcs.SetException(e); + } + }); + + return tcs.Task; + } - public static MenuFlyoutSubItem Add(this MenuFlyoutSubItem item, BasicFontFilter filter, Style style = null) - { - MenuFlyoutItem i = new() { Style = style }; - Core.Properties.SetFilter(i, filter); - item.Items.Add(i); - return item; - } + public static Task ExecuteAsync(this CoreDispatcher d, Action action, CoreDispatcherPriority p = CoreDispatcherPriority.Normal) + { + TaskCompletionSource tcs = new (); - public static T AddKeyboardAccelerator(this T u, VirtualKey key, VirtualKeyModifiers modifiers) where T : UIElement + _ = d.RunAsync(p, () => { - u.KeyboardAccelerators.Add(new KeyboardAccelerator { Key = key, Modifiers = modifiers }); - return u; - } + try + { + action(); + tcs.SetResult(true); + } + catch (Exception e) + { + tcs.SetException(e); + } + }); - public static T SetVisible(this T e, bool b) where T : FrameworkElement - { - e.Visibility = b ? Visibility.Visible : Visibility.Collapsed; - return e; - } + return tcs.Task; + } - public static List TryGetChildren(this ItemsControl control) - { - //if (control.ItemsPanelRoot is null) // Calling measure forces ItemsPanelRoot to become inflated - // control.Measure(new Windows.Foundation.Size(100, 100)); + public static MenuFlyout AddSeparator(this MenuFlyout menu, bool isVisible = true) + { + menu.Items.Add(new MenuFlyoutSeparator().SetVisible(isVisible)); + return menu; + } - return new List { control }; - } + public static MenuFlyoutSubItem Add(this MenuFlyoutSubItem item, BasicFontFilter filter, Style style = null) + { + MenuFlyoutItem i = new() { Style = style }; + Core.Properties.SetFilter(i, filter); + item.Items.Add(i); + return item; + } - public static T Realize(this T list, double width = 100, double height = 100) where T : ItemsControl - { - if (list.ItemsPanelRoot == null) - list.Measure(new (width, height)); + public static T AddKeyboardAccelerator(this T u, VirtualKey key, VirtualKeyModifiers modifiers) where T : UIElement + { + u.KeyboardAccelerators.Add(new KeyboardAccelerator { Key = key, Modifiers = modifiers }); + return u; + } - return list; - } + public static T SetVisible(this T e, bool b) where T : FrameworkElement + { + e.Visibility = b ? Visibility.Visible : Visibility.Collapsed; + return e; + } - public static T Merge(this T dictionary, ResourceDictionary d) where T : ResourceDictionary - { - dictionary.MergedDictionaries.Add(d); - return dictionary; - } + public static List TryGetChildren(this ItemsControl control) + { + //if (control.ItemsPanelRoot is null) // Calling measure forces ItemsPanelRoot to become inflated + // control.Measure(new Windows.Foundation.Size(100, 100)); - public static T MergeMUXC(this T dictionary, ControlsResourcesVersion version) where T : ResourceDictionary - { - dictionary.MergedDictionaries.Add(new XamlControlsResources { ControlsResourcesVersion = version }); - return dictionary; - } + return new List { control }; + } - public static T Merge(this T dictionary, string source) where T : ResourceDictionary - { - dictionary.MergedDictionaries.Add(new () { Source = new Uri(source) }); - return dictionary; - } + public static T Realize(this T list, double width = 100, double height = 100) where T : ItemsControl + { + if (list.ItemsPanelRoot == null) + list.Measure(new (width, height)); - public static Task ExtractToFolderAsync( - this ZipArchiveEntry entry, StorageFolder targetFolder, string fileName, CreationCollisionOption option) + return list; + } + + public static T Merge(this T dictionary, ResourceDictionary d) where T : ResourceDictionary + { + dictionary.MergedDictionaries.Add(d); + return dictionary; + } + + public static T MergeMUXC(this T dictionary, ControlsResourcesVersion version) where T : ResourceDictionary + { + dictionary.MergedDictionaries.Add(new XamlControlsResources { ControlsResourcesVersion = version }); + return dictionary; + } + + public static T Merge(this T dictionary, string source) where T : ResourceDictionary + { + dictionary.MergedDictionaries.Add(new () { Source = new Uri(source) }); + return dictionary; + } + + public static Task ExtractToFolderAsync( + this ZipArchiveEntry entry, StorageFolder targetFolder, string fileName, CreationCollisionOption option) + { + return Task.Run(async () => { - return Task.Run(async () => - { - using var s = entry.Open(); - var file = await targetFolder.CreateFileAsync($"{Path.GetRandomFileName()}.{Path.GetExtension(entry.Name)}", option).AsTask().ConfigureAwait(false); - using var fs = await file.OpenStreamForWriteAsync().ConfigureAwait(false); - s.CopyTo(fs); - fs.Flush(); - - return file; - }); - } + using var s = entry.Open(); + var file = await targetFolder.CreateFileAsync($"{Path.GetRandomFileName()}.{Path.GetExtension(entry.Name)}", option).AsTask().ConfigureAwait(false); + using var fs = await file.OpenStreamForWriteAsync().ConfigureAwait(false); + s.CopyTo(fs); + fs.Flush(); + + return file; + }); } } diff --git a/CharacterMap/CharacterMap/Helpers/FluentAnimation.cs b/CharacterMap/CharacterMap/Helpers/FluentAnimation.cs index 71bf379f..9b813843 100644 --- a/CharacterMap/CharacterMap/Helpers/FluentAnimation.cs +++ b/CharacterMap/CharacterMap/Helpers/FluentAnimation.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Windows.UI.Composition; +using Windows.UI.Composition; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Data; diff --git a/CharacterMap/CharacterMap/Helpers/FlyoutHelper.cs b/CharacterMap/CharacterMap/Helpers/FlyoutHelper.cs index a2a774e2..3dc568b3 100644 --- a/CharacterMap/CharacterMap/Helpers/FlyoutHelper.cs +++ b/CharacterMap/CharacterMap/Helpers/FlyoutHelper.cs @@ -1,644 +1,632 @@ using CharacterMap.Controls; -using CharacterMap.Core; -using CharacterMap.Models; -using CharacterMap.Provider; -using CharacterMap.Services; -using CharacterMap.ViewModels; using CharacterMap.Views; -using CharacterMapCX; -using CommunityToolkit.Mvvm.DependencyInjection; -using CommunityToolkit.Mvvm.Messaging; -using System; -using System.Collections.Generic; -using System.Linq; using Windows.System; using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; -namespace CharacterMap.Helpers +namespace CharacterMap.Helpers; + +public class FlyoutArgs { - public class FlyoutArgs - { - /// - /// A window showing a folder of fonts - /// - public bool IsFolderView => Folder is not null; - - /// - /// Folder of fonts associated with the view that initiates - /// the flyout menu - /// - public FolderContents Folder { get; set; } - - /// - /// ... menu - /// - public bool ShowAdvanced { get; set; } - - /// - /// A stand-alone Window showing a single Font Family - /// - public bool Standalone { get; set; } - - /// - /// Context menu for font tab headers - /// - public bool IsTabContext { get; set; } - - /// - /// The font family is from file that is not installed in the system - /// or imported into the app. (I.e., opened via Drag & Drop or open - /// button) - /// - public bool IsExternalFile { get; set; } - - public string PreviewText { get; set; } - - public Action AddToCollectionCommand { get; set; } - } + /// + /// A window showing a folder of fonts + /// + public bool IsFolderView => Folder is not null; + + /// + /// Folder of fonts associated with the view that initiates + /// the flyout menu + /// + public FolderContents Folder { get; set; } + + /// + /// ... menu + /// + public bool ShowAdvanced { get; set; } + + /// + /// A stand-alone Window showing a single Font Family + /// + public bool Standalone { get; set; } + + /// + /// Context menu for font tab headers + /// + public bool IsTabContext { get; set; } + + /// + /// The font family is from file that is not installed in the system + /// or imported into the app. (I.e., opened via Drag & Drop or open + /// button) + /// + public bool IsExternalFile { get; set; } + + public string PreviewText { get; set; } + + public Action AddToCollectionCommand { get; set; } +} - public static class FlyoutHelper - { - private static UserCollectionsService _collections { get; } = Ioc.Default.GetService(); +public static class FlyoutHelper +{ + private static UserCollectionsService _collections { get; } = Ioc.Default.GetService(); - public static void RequestDelete(InstalledFont font) + public static void RequestDelete(InstalledFont font) + { + MainViewModel main = Ioc.Default.GetService(); + var d = new ContentDialog { - MainViewModel main = Ioc.Default.GetService(); - var d = new ContentDialog - { - Title = Localization.Get("DlgDeleteFont/Title"), - IsPrimaryButtonEnabled = true, - IsSecondaryButtonEnabled = true, - PrimaryButtonText = Localization.Get("DigDeleteCollection/PrimaryButtonText"), - SecondaryButtonText = Localization.Get("DigDeleteCollection/SecondaryButtonText"), - }; - - d.PrimaryButtonClick += (ds, de) => + Title = Localization.Get("DlgDeleteFont/Title"), + IsPrimaryButtonEnabled = true, + IsSecondaryButtonEnabled = true, + PrimaryButtonText = Localization.Get("DigDeleteCollection/PrimaryButtonText"), + SecondaryButtonText = Localization.Get("DigDeleteCollection/SecondaryButtonText"), + }; + + d.PrimaryButtonClick += (ds, de) => + { + _ = MainPage.MainDispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { - _ = MainPage.MainDispatcher.RunAsync(CoreDispatcherPriority.Normal, () => - { - main.TryRemoveFont(font); - }); - }; - _ = d.ShowAsync(); - } + main.TryRemoveFont(font); + }); + }; + _ = d.ShowAsync(); + } - public static void PrintRequested() - { - WeakReferenceMessenger.Default.Send(new PrintRequestedMessage()); - } + public static void PrintRequested() + { + WeakReferenceMessenger.Default.Send(new PrintRequestedMessage()); + } - /// - /// Creates the context menu for the Font List or the "..." button. - /// Both of these have a font as their main target. - /// - /// - /// - /// - /// - public static void CreateMenu( - MenuFlyout menu, - InstalledFont font, - CharacterRenderingOptions options, - FrameworkElement headerContent, - FlyoutArgs args) - { - MainViewModel main = Ioc.Default.GetService(); + /// + /// Creates the context menu for the Font List or the "..." button. + /// Both of these have a font as their main target. + /// + /// + /// + /// + /// + public static void CreateMenu( + MenuFlyout menu, + InstalledFont font, + CharacterRenderingOptions options, + FrameworkElement headerContent, + FlyoutArgs args) + { + MainViewModel main = Ioc.Default.GetService(); - bool standalone = args.Standalone; - bool showAdvanced = args.ShowAdvanced; - bool isExternalFile = args.IsExternalFile; + bool standalone = args.Standalone; + bool showAdvanced = args.ShowAdvanced; + bool isExternalFile = args.IsExternalFile; - Style style = ResourceHelper.Get + + + + +
diff --git a/CharacterMap/CharacterMap/Themes/SystemThemes.xaml b/CharacterMap/CharacterMap/Themes/SystemThemes.xaml index 3a44d82c..694eb4af 100644 --- a/CharacterMap/CharacterMap/Themes/SystemThemes.xaml +++ b/CharacterMap/CharacterMap/Themes/SystemThemes.xaml @@ -48,6 +48,9 @@ + + + diff --git a/CharacterMap/CharacterMap/ViewModels/FontMapViewModel.cs b/CharacterMap/CharacterMap/ViewModels/FontMapViewModel.cs index fca1ad6b..7b5756ae 100644 --- a/CharacterMap/CharacterMap/ViewModels/FontMapViewModel.cs +++ b/CharacterMap/CharacterMap/ViewModels/FontMapViewModel.cs @@ -1,6 +1,7 @@ using CharacterMap.Views; using CommunityToolkit.Mvvm.Input; using Microsoft.Graphics.Canvas.Text; +using System.Collections; using System.Collections.ObjectModel; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; @@ -68,7 +69,7 @@ public partial class FontMapViewModel : ViewModelBase [ObservableProperty] UnihanData _unihanData; [ObservableProperty] IReadOnlyList _rampOptions; [ObservableProperty] IReadOnlyList _chars; - [ObservableProperty] IReadOnlyList _searchResults; + [ObservableProperty] IEnumerable _searchResults; [ObservableProperty] IReadOnlyList _variationAxis; [ObservableProperty] IReadOnlyList _providers; [ObservableProperty] IReadOnlyList _typographyFeatures; @@ -78,6 +79,7 @@ public partial class FontMapViewModel : ViewModelBase [ObservableProperty] bool _importButtonEnabled = true; [ObservableProperty] bool _showingUnihan = true; [ObservableProperty] bool _isLoading; + [ObservableProperty] bool _isSearchGrouped; [ObservableProperty] bool _hasFontOptions; [ObservableProperty] bool _isSvgChar; [ObservableProperty] bool _isSequenceRootVisible; @@ -562,6 +564,23 @@ internal async void Search(string query) if (await GlyphService.SearchAsync(query, SelectedVariant) is IReadOnlyList results && token.IsValid()) { + SearchResults = null; + IsSearchGrouped = false; + + if (results.Count == 0) + results = null; + + if (results != null && SelectedGlyphCategories.Any(c => c.IsSelected is false)) + { + var groups = SearchResultsGroup.CreateGroups(results, SelectedGlyphCategories); + if (groups[1].Count > 0) + { + IsSearchGrouped = true; + SearchResults = groups; + return; + } + } + SearchResults = results; } } diff --git a/CharacterMap/CharacterMap/Views/FontMapView.Animation.cs b/CharacterMap/CharacterMap/Views/FontMapView.Animation.cs index 3cab3314..af41b448 100644 --- a/CharacterMap/CharacterMap/Views/FontMapView.Animation.cs +++ b/CharacterMap/CharacterMap/Views/FontMapView.Animation.cs @@ -163,8 +163,8 @@ public void UpdateGridToRampTransition() .Where(c => c.IsInViewport(CharGrid)) .Select(c => c is GridViewHeaderItem hi ? (FrameworkElement)hi.ContentTemplateRoot : c); ; - if (CharGrid.Header is FrameworkElement header) - clds = clds.Append(header); + if (CharGrid.Header is Panel header && header.Children.Count > 0) + clds = clds.Concat(header.Children.OfType()); List childs = clds.OrderBy(c => Guid.NewGuid()).ToList(); List toChilds = GetTypeRampAnimationTargets(); @@ -307,7 +307,7 @@ public void UpdateRampToGridTransition() .OfType() .Where(c => c.IsInViewport(CharGrid)) .Select(c => c is GridViewHeaderItem hi ? (FrameworkElement)hi.ContentTemplateRoot : c) - .Append(CharGrid.Header as FrameworkElement) + .Concat(((Panel)CharGrid.Header).Children.OfType()) .Where(c => c is not null) .OrderBy(c => Guid.NewGuid()) .ToList(); @@ -318,7 +318,7 @@ public void UpdateRampToGridTransition() .OfType() .Where(c => c.IsInViewport(this)) .Select(c => c is GridViewHeaderItem hi ? (FrameworkElement)hi.ContentTemplateRoot : c) - .Append(CharGrid.Header as FrameworkElement) + .Concat(((Panel)CharGrid.Header).Children.OfType()) .Where(c => c is not null) .OrderBy(c => Guid.NewGuid()) .ToList(); diff --git a/CharacterMap/CharacterMap/Views/FontMapView.xaml b/CharacterMap/CharacterMap/Views/FontMapView.xaml index 1222ae37..13485e3b 100644 --- a/CharacterMap/CharacterMap/Views/FontMapView.xaml +++ b/CharacterMap/CharacterMap/Views/FontMapView.xaml @@ -150,6 +150,12 @@ IsSourceGrouped="True" Source="{Binding ViewModel.GroupedChars, ElementName=ViewRoot, Mode=OneWay}" /> + + - - - - - - - - - - + + + + + + + + - - - - + Glyph="" /> + + + + + + + + + + + + + + + + + + + + + + @@ -1388,7 +1433,7 @@ @@ -1420,7 +1465,7 @@ Width="36" Height="36" MinWidth="36" - Margin="8 -4 0 -4" + Margin="8 -4 12 -4" HorizontalAlignment="Right" VerticalAlignment="Center" helpers:FluentAnimation.PointerOver="FluentContentPresenter" diff --git a/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs b/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs index a26020ed..cff8ad65 100644 --- a/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs +++ b/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs @@ -695,7 +695,7 @@ private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e) private void OnSearchBoxGotFocus(AutoSuggestBox searchBox) { - if (ViewModel.SearchResults != null && ViewModel.SearchResults.Count > 0) + if (ViewModel.SearchResults is not null) searchBox.IsSuggestionListOpen = true; else ViewModel.DebounceSearch(ViewModel.SearchQuery, ViewModel.Settings.InstantSearchDelay); @@ -1022,6 +1022,11 @@ private void AdvancedSettings_Click(object sender, RoutedEventArgs e) Messenger.Send(); } + private void VariableHintClick(object sender, RoutedEventArgs e) + { + ViewToggleButton.Focus(FocusState.Keyboard); + } + From deedba469116edc49362ec8e9675b62bbf1f2c25 Mon Sep 17 00:00:00 2001 From: Johnny Westlake Date: Fri, 3 Nov 2023 13:59:40 +0000 Subject: [PATCH 23/31] Add notification for hidden search, add default Simplified Chinese suggestion --- .../Controls/FilterFlyout.xaml.cs | 12 +- .../CharacterMap/Core/PanoseParser.cs | 2 +- .../CharacterMap/Models/SearchResultsGroup.cs | 18 +- .../CharacterMap.de-DE.xlf | 254 ++++++++++++++++- .../CharacterMap.en-GB.xlf | 253 ++++++++++++++++- .../CharacterMap.fr-FR.xlf | 253 ++++++++++++++++- .../CharacterMap.it-IT.xlf | 254 ++++++++++++++++- .../CharacterMap.pl-PL.xlf | 252 ++++++++++++++++- .../CharacterMap.ru-RU.xlf | 253 ++++++++++++++++- .../MultilingualResources/CharacterMap.ta.xlf | 253 ++++++++++++++++- .../CharacterMap.th-TH.xlf | 254 ++++++++++++++++- .../CharacterMap.zh-CN.xlf | 257 +++++++++++++++++- .../CharacterMap.zh-TW.xlf | 253 ++++++++++++++++- .../CharacterMap/Services/GlyphService.cs | 1 + .../CharacterMap/Strings/en-US/Resources.resw | 25 +- .../CharacterMap/Strings/zh-CN/Resources.resw | 9 + .../Themes/FluentThemeStyles.xaml | 2 - CharacterMap/CharacterMap/Themes/Generic.xaml | 13 +- .../ViewModels/FontMapViewModel.cs | 7 +- .../CharacterMap/Views/FontMapView.xaml.cs | 11 +- 20 files changed, 2585 insertions(+), 51 deletions(-) diff --git a/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs b/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs index 2d7d9fb0..6636fd7f 100644 --- a/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs +++ b/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs @@ -70,23 +70,23 @@ private void Create() .Add(BasicFontFilter.ScriptCyrillic, style) .Add(BasicFontFilter.ScriptDevanagari, style) .Add(BasicFontFilter.ScriptGreekAndCoptic, style) + .Add(BasicFontFilter.ScriptCJKUnifiedIdeographs, style) .Add(BasicFontFilter.ScriptHiraganaAndKatakana, style) .Add(BasicFontFilter.ScriptHebrew, style) + .Add(BasicFontFilter.ScriptKoreanHangul, style) .Add(BasicFontFilter.ScriptBasicLatin, style) - .Add(BasicFontFilter.ScriptThai, style) - .Add(BasicFontFilter.ScriptCJKUnifiedIdeographs, style) - .Add(BasicFontFilter.ScriptKoreanHangul, style); + .Add(BasicFontFilter.ScriptThai, style); // 3. "More" option _ops = AddSub("OptionMoreFilters/Text") + .Add(BasicFontFilter.VariableFonts, style) .Add(BasicFontFilter.ColorFonts, style) .Add(BasicFontFilter.PanoseDecorativeFonts, style) .Add(BasicFontFilter.PanoseScriptFonts, style) - .Add(BasicFontFilter.MonospacedFonts, style) - .Add(BasicFontFilter.VariableFonts, style); + .Add(BasicFontFilter.MonospacedFonts, style); - _variableOption = _ops.Items.Last(); + _variableOption = _ops.Items[0]; AddChild(_ops, "OptionEmoji/Text") .Add(BasicFontFilter.EmojiAll, style) diff --git a/CharacterMap/CharacterMap/Core/PanoseParser.cs b/CharacterMap/CharacterMap/Core/PanoseParser.cs index 210c3d18..966826e1 100644 --- a/CharacterMap/CharacterMap/Core/PanoseParser.cs +++ b/CharacterMap/CharacterMap/Core/PanoseParser.cs @@ -26,7 +26,7 @@ public Panose(PanoseFamily family, PanoseSerifStyle style, DWriteProperties prop { _props = props; - HasPanose = hasPanose; + HasPanose = hasPanose && (family != PanoseFamily.Any && style != PanoseSerifStyle.ANY); Family = family; Style = style; IsSansSerifStyle = Style >= PanoseSerifStyle.NORMAL_SANS; diff --git a/CharacterMap/CharacterMap/Models/SearchResultsGroup.cs b/CharacterMap/CharacterMap/Models/SearchResultsGroup.cs index 328abb86..d104505a 100644 --- a/CharacterMap/CharacterMap/Models/SearchResultsGroup.cs +++ b/CharacterMap/CharacterMap/Models/SearchResultsGroup.cs @@ -1,5 +1,15 @@ namespace CharacterMap.Models; +public class SearchResultsGroups : List +{ + public bool HasHiddenResults { get; } + + public SearchResultsGroups(params SearchResultsGroup[] groups) : base(groups) + { + HasHiddenResults = groups.Length > 1 && groups[1].Count > 0; + } +} + public class SearchResultsGroup : List, IGrouping { public string Key { get; } @@ -9,16 +19,16 @@ public SearchResultsGroup(string key, IEnumerable data) : base(data) Key = key; } - public static List CreateGroups(IEnumerable items, List categories) + public static SearchResultsGroups CreateGroups(IEnumerable items, List categories) { SearchResultsGroup active = new(null, items.Where(g => categories.Where(c => c.IsSelected).Any(c => c.Range.Contains((uint)g.UnicodeIndex)))); - SearchResultsGroup inactive = new("HIDDEN", + SearchResultsGroup inactive = new( + Localization.Get("HiddenSearchResultsLabel/Text"), items.Except(active)); - return new() { active, inactive }; - + return new(active, inactive); } public override string ToString() => Key; diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.de-DE.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.de-DE.xlf index 4e70d6e5..7325c813 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.de-DE.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.de-DE.xlf @@ -960,8 +960,9 @@ Erwäge, die Details über diesen Fehler bei GitHub mitzuteilen, damit wir dir b Latein - CJK - CJK + CJK (Chinese/Japanese/Korean) + CJK + Please verify the translation’s accuracy as the source string was updated after it was translated. Cyrillic @@ -1770,6 +1771,255 @@ Tippen und halten zum umsortieren. Vietnamese Vietnamese + + {0} Font Families, {1} Font Faces installed on your computer + {0} Font Families, {1} Font Faces installed on your computer + + + Installed Fonts + Installed Fonts + + + Export installed fonts + Export installed fonts + + + By Design Language + By Design Language + + + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + + + Close + Close + + + Install Font + Install Font + + + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Word in such a way that encourages the user to read all the steps before continuing + + + Install + Install + + + By Unicode Range + By Unicode Range + + + Advanced + Advanced + + + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + + + Hide deprecated icons + Hide deprecated icons + + + Start + Start + + + Hex Value + Hex Value + + + Preview File + Preview File + + + {0} Font Families, {1} Font Faces imported + {0} Font Families, {1} Font Faces imported + + + Include version number in file name + Include version number in file name + + + Hide Unihan data + Hide Unihan data + + + Show Unihan data + Show Unihan data + + + Definition + Definition + + + Pronunciations + Pronunciations + + + The most customary jyutping (Cantonese) reading for this ideograph. + The most customary jyutping (Cantonese) reading for this ideograph. + + + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + + + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + + + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + + + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + + + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + + + The Sino-Japanese pronunciation(s) of this ideograph. + The Sino-Japanese pronunciation(s) of this ideograph. + + + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + + + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + + + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + + + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + + + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + + + The ideograph’s pronunciation(s) in Quốc ngữ. + The ideograph’s pronunciation(s) in Quốc ngữ. + + + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + + + Cantonese + Cantonese + + + Hangul + Hangul + + + Hanyu Pinlu + Hanyu Pinlu + + + Hànyǔ Pīnyīn + Hànyǔ Pīnyīn + + + Japanese + Japanese + + + Japanese Kun + Japanese Kun + + + Japanese On + Japanese On + + + Korean + Korean + + + Mandarin + Mandarin + + + SmSZ + SmSZ + + + Tang + Tang + + + Hànyǔ Pīnyīn (TGHZ) + Hànyǔ Pīnyīn (TGHZ) + + + Vietnamese + Vietnamese + + + Hànyǔ Pīnyīn (XHC) + Hànyǔ Pīnyīn (XHC) + + + HIDDEN + HIDDEN + + + Chinese (Simplified) + Chinese (Simplified) + + + The selected character is currently hidden by your selected filters + The selected character is currently hidden by your selected filters + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.en-GB.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.en-GB.xlf index a92e1838..3d88200f 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.en-GB.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.en-GB.xlf @@ -960,8 +960,8 @@ Please consider posting details about this error to GitHub so we can help you fi Latin - CJK - CJK + CJK (Chinese/Japanese/Korean) + CJK (Chinese/Japanese/Korean) Cyrillic @@ -1770,6 +1770,255 @@ Tap and hold to arrange. Vietnamese Vietnamese + + {0} Font Families, {1} Font Faces installed on your computer + {0} Font Families, {1} Font Faces installed on your computer + + + Installed Fonts + Installed Fonts + + + Export installed fonts + Export installed fonts + + + By Design Language + By Design Language + + + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + + + Close + Close + + + Install Font + Install Font + + + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Word in such a way that encourages the user to read all the steps before continuing + + + Install + Install + + + By Unicode Range + By Unicode Range + + + Advanced + Advanced + + + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + + + Hide deprecated icons + Hide deprecated icons + + + Start + Start + + + Hex Value + Hex Value + + + Preview File + Preview File + + + {0} Font Families, {1} Font Faces imported + {0} Font Families, {1} Font Faces imported + + + Include version number in file name + Include version number in file name + + + Hide Unihan data + Hide Unihan data + + + Show Unihan data + Show Unihan data + + + Definition + Definition + + + Pronunciations + Pronunciations + + + The most customary jyutping (Cantonese) reading for this ideograph. + The most customary jyutping (Cantonese) reading for this ideograph. + + + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + + + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + + + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + + + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + + + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + + + The Sino-Japanese pronunciation(s) of this ideograph. + The Sino-Japanese pronunciation(s) of this ideograph. + + + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + + + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + + + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + + + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + + + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + + + The ideograph’s pronunciation(s) in Quốc ngữ. + The ideograph’s pronunciation(s) in Quốc ngữ. + + + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + + + Cantonese + Cantonese + + + Hangul + Hangul + + + Hanyu Pinlu + Hanyu Pinlu + + + Hànyǔ Pīnyīn + Hànyǔ Pīnyīn + + + Japanese + Japanese + + + Japanese Kun + Japanese Kun + + + Japanese On + Japanese On + + + Korean + Korean + + + Mandarin + Mandarin + + + SmSZ + SmSZ + + + Tang + Tang + + + Hànyǔ Pīnyīn (TGHZ) + Hànyǔ Pīnyīn (TGHZ) + + + Vietnamese + Vietnamese + + + Hànyǔ Pīnyīn (XHC) + Hànyǔ Pīnyīn (XHC) + + + HIDDEN + HIDDEN + + + Chinese (Simplified) + Chinese (Simplified) + + + The selected character is currently hidden by your selected filters + The selected character is currently hidden by your selected filters + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.fr-FR.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.fr-FR.xlf index d8b41d13..1fece5b2 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.fr-FR.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.fr-FR.xlf @@ -962,8 +962,8 @@ Please consider posting details about this error to GitHub so we can help you fi Latin - CJK - CJK + CJK (Chinese/Japanese/Korean) + CJK (Chinese/Japanese/Korean) Cyrillic @@ -1772,6 +1772,255 @@ Tap and hold to arrange. Vietnamese Vietnamese + + {0} Font Families, {1} Font Faces installed on your computer + {0} Font Families, {1} Font Faces installed on your computer + + + Installed Fonts + Installed Fonts + + + Export installed fonts + Export installed fonts + + + By Design Language + By Design Language + + + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + + + Close + Close + + + Install Font + Install Font + + + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Word in such a way that encourages the user to read all the steps before continuing + + + Install + Install + + + By Unicode Range + By Unicode Range + + + Advanced + Advanced + + + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + + + Hide deprecated icons + Hide deprecated icons + + + Start + Start + + + Hex Value + Hex Value + + + Preview File + Preview File + + + {0} Font Families, {1} Font Faces imported + {0} Font Families, {1} Font Faces imported + + + Include version number in file name + Include version number in file name + + + Hide Unihan data + Hide Unihan data + + + Show Unihan data + Show Unihan data + + + Definition + Definition + + + Pronunciations + Pronunciations + + + The most customary jyutping (Cantonese) reading for this ideograph. + The most customary jyutping (Cantonese) reading for this ideograph. + + + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + + + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + + + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + + + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + + + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + + + The Sino-Japanese pronunciation(s) of this ideograph. + The Sino-Japanese pronunciation(s) of this ideograph. + + + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + + + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + + + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + + + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + + + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + + + The ideograph’s pronunciation(s) in Quốc ngữ. + The ideograph’s pronunciation(s) in Quốc ngữ. + + + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + + + Cantonese + Cantonese + + + Hangul + Hangul + + + Hanyu Pinlu + Hanyu Pinlu + + + Hànyǔ Pīnyīn + Hànyǔ Pīnyīn + + + Japanese + Japanese + + + Japanese Kun + Japanese Kun + + + Japanese On + Japanese On + + + Korean + Korean + + + Mandarin + Mandarin + + + SmSZ + SmSZ + + + Tang + Tang + + + Hànyǔ Pīnyīn (TGHZ) + Hànyǔ Pīnyīn (TGHZ) + + + Vietnamese + Vietnamese + + + Hànyǔ Pīnyīn (XHC) + Hànyǔ Pīnyīn (XHC) + + + HIDDEN + HIDDEN + + + Chinese (Simplified) + Chinese (Simplified) + + + The selected character is currently hidden by your selected filters + The selected character is currently hidden by your selected filters + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.it-IT.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.it-IT.xlf index e03c703b..5a0e4fe4 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.it-IT.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.it-IT.xlf @@ -963,8 +963,9 @@ Ti consigliamo di pubblicare i dettagli di questo errore su GitHub in modo che p Latino - CJK - Cinese Giapponese Coreano + CJK (Chinese/Japanese/Korean) + Cinese Giapponese Coreano + Please verify the translation’s accuracy as the source string was updated after it was translated. Cyrillic @@ -1775,6 +1776,255 @@ Tocca e tieni premuto per disporre. Vietnamese Vietnamese + + {0} Font Families, {1} Font Faces installed on your computer + {0} Font Families, {1} Font Faces installed on your computer + + + Installed Fonts + Installed Fonts + + + Export installed fonts + Export installed fonts + + + By Design Language + By Design Language + + + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + + + Close + Close + + + Install Font + Install Font + + + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Word in such a way that encourages the user to read all the steps before continuing + + + Install + Install + + + By Unicode Range + By Unicode Range + + + Advanced + Advanced + + + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + + + Hide deprecated icons + Hide deprecated icons + + + Start + Start + + + Hex Value + Hex Value + + + Preview File + Preview File + + + {0} Font Families, {1} Font Faces imported + {0} Font Families, {1} Font Faces imported + + + Include version number in file name + Include version number in file name + + + Hide Unihan data + Hide Unihan data + + + Show Unihan data + Show Unihan data + + + Definition + Definition + + + Pronunciations + Pronunciations + + + The most customary jyutping (Cantonese) reading for this ideograph. + The most customary jyutping (Cantonese) reading for this ideograph. + + + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + + + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + + + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + + + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + + + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + + + The Sino-Japanese pronunciation(s) of this ideograph. + The Sino-Japanese pronunciation(s) of this ideograph. + + + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + + + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + + + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + + + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + + + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + + + The ideograph’s pronunciation(s) in Quốc ngữ. + The ideograph’s pronunciation(s) in Quốc ngữ. + + + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + + + Cantonese + Cantonese + + + Hangul + Hangul + + + Hanyu Pinlu + Hanyu Pinlu + + + Hànyǔ Pīnyīn + Hànyǔ Pīnyīn + + + Japanese + Japanese + + + Japanese Kun + Japanese Kun + + + Japanese On + Japanese On + + + Korean + Korean + + + Mandarin + Mandarin + + + SmSZ + SmSZ + + + Tang + Tang + + + Hànyǔ Pīnyīn (TGHZ) + Hànyǔ Pīnyīn (TGHZ) + + + Vietnamese + Vietnamese + + + Hànyǔ Pīnyīn (XHC) + Hànyǔ Pīnyīn (XHC) + + + HIDDEN + HIDDEN + + + Chinese (Simplified) + Chinese (Simplified) + + + The selected character is currently hidden by your selected filters + The selected character is currently hidden by your selected filters + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.pl-PL.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.pl-PL.xlf index 75e95061..37bdd57f 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.pl-PL.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.pl-PL.xlf @@ -963,8 +963,9 @@ Rozważ opublikowanie szczegółów dotyczących tego błędu w usłudze GitHub, Łacina - CJK + CJK (Chinese/Japanese/Korean) CJK + Please verify the translation’s accuracy as the source string was updated after it was translated. Cyrillic @@ -1773,6 +1774,255 @@ Dotknij i przytrzymaj, aby rozmieścić. Vietnamese Vietnamese + + {0} Font Families, {1} Font Faces installed on your computer + {0} Font Families, {1} Font Faces installed on your computer + + + Installed Fonts + Installed Fonts + + + Export installed fonts + Export installed fonts + + + By Design Language + By Design Language + + + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + + + Close + Close + + + Install Font + Install Font + + + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Word in such a way that encourages the user to read all the steps before continuing + + + Install + Install + + + By Unicode Range + By Unicode Range + + + Advanced + Advanced + + + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + + + Hide deprecated icons + Hide deprecated icons + + + Start + Start + + + Hex Value + Hex Value + + + Preview File + Preview File + + + {0} Font Families, {1} Font Faces imported + {0} Font Families, {1} Font Faces imported + + + Include version number in file name + Include version number in file name + + + Hide Unihan data + Hide Unihan data + + + Show Unihan data + Show Unihan data + + + Definition + Definition + + + Pronunciations + Pronunciations + + + The most customary jyutping (Cantonese) reading for this ideograph. + The most customary jyutping (Cantonese) reading for this ideograph. + + + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + + + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + + + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + + + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + + + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + + + The Sino-Japanese pronunciation(s) of this ideograph. + The Sino-Japanese pronunciation(s) of this ideograph. + + + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + + + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + + + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + + + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + + + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + + + The ideograph’s pronunciation(s) in Quốc ngữ. + The ideograph’s pronunciation(s) in Quốc ngữ. + + + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + + + Cantonese + Cantonese + + + Hangul + Hangul + + + Hanyu Pinlu + Hanyu Pinlu + + + Hànyǔ Pīnyīn + Hànyǔ Pīnyīn + + + Japanese + Japanese + + + Japanese Kun + Japanese Kun + + + Japanese On + Japanese On + + + Korean + Korean + + + Mandarin + Mandarin + + + SmSZ + SmSZ + + + Tang + Tang + + + Hànyǔ Pīnyīn (TGHZ) + Hànyǔ Pīnyīn (TGHZ) + + + Vietnamese + Vietnamese + + + Hànyǔ Pīnyīn (XHC) + Hànyǔ Pīnyīn (XHC) + + + HIDDEN + HIDDEN + + + Chinese (Simplified) + Chinese (Simplified) + + + The selected character is currently hidden by your selected filters + The selected character is currently hidden by your selected filters + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ru-RU.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ru-RU.xlf index 58f0648a..6561494b 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ru-RU.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ru-RU.xlf @@ -962,8 +962,8 @@ Please consider posting details about this error to GitHub so we can help you fi Latin - CJK - CJK + CJK (Chinese/Japanese/Korean) + CJK (Chinese/Japanese/Korean) Cyrillic @@ -1772,6 +1772,255 @@ Tap and hold to arrange. Vietnamese Vietnamese + + {0} Font Families, {1} Font Faces installed on your computer + {0} Font Families, {1} Font Faces installed on your computer + + + Installed Fonts + Installed Fonts + + + Export installed fonts + Export installed fonts + + + By Design Language + By Design Language + + + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + + + Close + Close + + + Install Font + Install Font + + + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Word in such a way that encourages the user to read all the steps before continuing + + + Install + Install + + + By Unicode Range + By Unicode Range + + + Advanced + Advanced + + + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + + + Hide deprecated icons + Hide deprecated icons + + + Start + Start + + + Hex Value + Hex Value + + + Preview File + Preview File + + + {0} Font Families, {1} Font Faces imported + {0} Font Families, {1} Font Faces imported + + + Include version number in file name + Include version number in file name + + + Hide Unihan data + Hide Unihan data + + + Show Unihan data + Show Unihan data + + + Definition + Definition + + + Pronunciations + Pronunciations + + + The most customary jyutping (Cantonese) reading for this ideograph. + The most customary jyutping (Cantonese) reading for this ideograph. + + + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + + + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + + + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + + + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + + + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + + + The Sino-Japanese pronunciation(s) of this ideograph. + The Sino-Japanese pronunciation(s) of this ideograph. + + + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + + + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + + + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + + + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + + + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + + + The ideograph’s pronunciation(s) in Quốc ngữ. + The ideograph’s pronunciation(s) in Quốc ngữ. + + + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + + + Cantonese + Cantonese + + + Hangul + Hangul + + + Hanyu Pinlu + Hanyu Pinlu + + + Hànyǔ Pīnyīn + Hànyǔ Pīnyīn + + + Japanese + Japanese + + + Japanese Kun + Japanese Kun + + + Japanese On + Japanese On + + + Korean + Korean + + + Mandarin + Mandarin + + + SmSZ + SmSZ + + + Tang + Tang + + + Hànyǔ Pīnyīn (TGHZ) + Hànyǔ Pīnyīn (TGHZ) + + + Vietnamese + Vietnamese + + + Hànyǔ Pīnyīn (XHC) + Hànyǔ Pīnyīn (XHC) + + + HIDDEN + HIDDEN + + + Chinese (Simplified) + Chinese (Simplified) + + + The selected character is currently hidden by your selected filters + The selected character is currently hidden by your selected filters + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ta.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ta.xlf index ff3ff627..550f4ddf 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ta.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ta.xlf @@ -971,8 +971,8 @@ Please consider posting details about this error to GitHub so we can help you fi லத்தீன் - CJK - CJK + CJK (Chinese/Japanese/Korean) + CJK (Chinese/Japanese/Korean) Cyrillic @@ -1784,6 +1784,255 @@ Tap and hold to arrange. Vietnamese Vietnamese + + {0} Font Families, {1} Font Faces installed on your computer + {0} Font Families, {1} Font Faces installed on your computer + + + Installed Fonts + Installed Fonts + + + Export installed fonts + Export installed fonts + + + By Design Language + By Design Language + + + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + + + Close + Close + + + Install Font + Install Font + + + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Word in such a way that encourages the user to read all the steps before continuing + + + Install + Install + + + By Unicode Range + By Unicode Range + + + Advanced + Advanced + + + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + + + Hide deprecated icons + Hide deprecated icons + + + Start + Start + + + Hex Value + Hex Value + + + Preview File + Preview File + + + {0} Font Families, {1} Font Faces imported + {0} Font Families, {1} Font Faces imported + + + Include version number in file name + Include version number in file name + + + Hide Unihan data + Hide Unihan data + + + Show Unihan data + Show Unihan data + + + Definition + Definition + + + Pronunciations + Pronunciations + + + The most customary jyutping (Cantonese) reading for this ideograph. + The most customary jyutping (Cantonese) reading for this ideograph. + + + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + + + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + + + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + + + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + + + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + + + The Sino-Japanese pronunciation(s) of this ideograph. + The Sino-Japanese pronunciation(s) of this ideograph. + + + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + + + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + + + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + + + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + + + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + + + The ideograph’s pronunciation(s) in Quốc ngữ. + The ideograph’s pronunciation(s) in Quốc ngữ. + + + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + + + Cantonese + Cantonese + + + Hangul + Hangul + + + Hanyu Pinlu + Hanyu Pinlu + + + Hànyǔ Pīnyīn + Hànyǔ Pīnyīn + + + Japanese + Japanese + + + Japanese Kun + Japanese Kun + + + Japanese On + Japanese On + + + Korean + Korean + + + Mandarin + Mandarin + + + SmSZ + SmSZ + + + Tang + Tang + + + Hànyǔ Pīnyīn (TGHZ) + Hànyǔ Pīnyīn (TGHZ) + + + Vietnamese + Vietnamese + + + Hànyǔ Pīnyīn (XHC) + Hànyǔ Pīnyīn (XHC) + + + HIDDEN + HIDDEN + + + Chinese (Simplified) + Chinese (Simplified) + + + The selected character is currently hidden by your selected filters + The selected character is currently hidden by your selected filters + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.th-TH.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.th-TH.xlf index 1f8367f1..d9544935 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.th-TH.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.th-TH.xlf @@ -965,8 +965,9 @@ Please consider posting details about this error to GitHub so we can help you fi ละติน - CJK - จีน/ญี่ปุ่น/เกาหลี + CJK (Chinese/Japanese/Korean) + จีน/ญี่ปุ่น/เกาหลี + Please verify the translation’s accuracy as the source string was updated after it was translated. Cyrillic @@ -1778,6 +1779,255 @@ Tap and hold to arrange. Vietnamese Vietnamese + + {0} Font Families, {1} Font Faces installed on your computer + {0} Font Families, {1} Font Faces installed on your computer + + + Installed Fonts + Installed Fonts + + + Export installed fonts + Export installed fonts + + + By Design Language + By Design Language + + + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + + + Close + Close + + + Install Font + Install Font + + + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Word in such a way that encourages the user to read all the steps before continuing + + + Install + Install + + + By Unicode Range + By Unicode Range + + + Advanced + Advanced + + + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + + + Hide deprecated icons + Hide deprecated icons + + + Start + Start + + + Hex Value + Hex Value + + + Preview File + Preview File + + + {0} Font Families, {1} Font Faces imported + {0} Font Families, {1} Font Faces imported + + + Include version number in file name + Include version number in file name + + + Hide Unihan data + Hide Unihan data + + + Show Unihan data + Show Unihan data + + + Definition + Definition + + + Pronunciations + Pronunciations + + + The most customary jyutping (Cantonese) reading for this ideograph. + The most customary jyutping (Cantonese) reading for this ideograph. + + + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + + + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + + + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + + + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + + + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + + + The Sino-Japanese pronunciation(s) of this ideograph. + The Sino-Japanese pronunciation(s) of this ideograph. + + + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + + + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + + + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + + + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + + + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + + + The ideograph’s pronunciation(s) in Quốc ngữ. + The ideograph’s pronunciation(s) in Quốc ngữ. + + + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + + + Cantonese + Cantonese + + + Hangul + Hangul + + + Hanyu Pinlu + Hanyu Pinlu + + + Hànyǔ Pīnyīn + Hànyǔ Pīnyīn + + + Japanese + Japanese + + + Japanese Kun + Japanese Kun + + + Japanese On + Japanese On + + + Korean + Korean + + + Mandarin + Mandarin + + + SmSZ + SmSZ + + + Tang + Tang + + + Hànyǔ Pīnyīn (TGHZ) + Hànyǔ Pīnyīn (TGHZ) + + + Vietnamese + Vietnamese + + + Hànyǔ Pīnyīn (XHC) + Hànyǔ Pīnyīn (XHC) + + + HIDDEN + HIDDEN + + + Chinese (Simplified) + Chinese (Simplified) + + + The selected character is currently hidden by your selected filters + The selected character is currently hidden by your selected filters + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-CN.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-CN.xlf index 01ed94fa..c74943d0 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-CN.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-CN.xlf @@ -972,8 +972,8 @@ Please consider posting details about this error to GitHub so we can help you fi 拉丁语 - CJK - CJK + CJK (Chinese/Japanese/Korean) + CJK (Chinese/Japanese/Korean) Cyrillic @@ -993,7 +993,7 @@ Please consider posting details about this error to GitHub so we can help you fi How vexingly quick daft zebras jump! 1234567890 - How vexingly quick daft zebras jump! 1234567890 + 有朋自远方来 不亦乐乎 Switch to Character Map view (Ctrl+T) @@ -1728,7 +1728,7 @@ Tap and hold to arrange. English - English + 简体中文 Custom @@ -1786,6 +1786,255 @@ Tap and hold to arrange. Vietnamese Vietnamese + + {0} Font Families, {1} Font Faces installed on your computer + {0} Font Families, {1} Font Faces installed on your computer + + + Installed Fonts + Installed Fonts + + + Export installed fonts + Export installed fonts + + + By Design Language + By Design Language + + + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + + + Close + Close + + + Install Font + Install Font + + + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Word in such a way that encourages the user to read all the steps before continuing + + + Install + Install + + + By Unicode Range + By Unicode Range + + + Advanced + Advanced + + + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + + + Hide deprecated icons + Hide deprecated icons + + + Start + Start + + + Hex Value + Hex Value + + + Preview File + Preview File + + + {0} Font Families, {1} Font Faces imported + {0} Font Families, {1} Font Faces imported + + + Include version number in file name + Include version number in file name + + + Hide Unihan data + Hide Unihan data + + + Show Unihan data + Show Unihan data + + + Definition + Definition + + + Pronunciations + Pronunciations + + + The most customary jyutping (Cantonese) reading for this ideograph. + The most customary jyutping (Cantonese) reading for this ideograph. + + + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + + + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + + + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + + + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + + + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + + + The Sino-Japanese pronunciation(s) of this ideograph. + The Sino-Japanese pronunciation(s) of this ideograph. + + + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + + + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + + + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + + + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + + + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + + + The ideograph’s pronunciation(s) in Quốc ngữ. + The ideograph’s pronunciation(s) in Quốc ngữ. + + + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + + + Cantonese + Cantonese + + + Hangul + Hangul + + + Hanyu Pinlu + Hanyu Pinlu + + + Hànyǔ Pīnyīn + Hànyǔ Pīnyīn + + + Japanese + Japanese + + + Japanese Kun + Japanese Kun + + + Japanese On + Japanese On + + + Korean + Korean + + + Mandarin + Mandarin + + + SmSZ + SmSZ + + + Tang + Tang + + + Hànyǔ Pīnyīn (TGHZ) + Hànyǔ Pīnyīn (TGHZ) + + + Vietnamese + Vietnamese + + + Hànyǔ Pīnyīn (XHC) + Hànyǔ Pīnyīn (XHC) + + + HIDDEN + HIDDEN + + + Chinese (Simplified) + 简体中文 + + + The selected character is currently hidden by your selected filters + The selected character is currently hidden by your selected filters + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-TW.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-TW.xlf index 2c0516a3..db18e280 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-TW.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-TW.xlf @@ -965,8 +965,8 @@ Please consider posting details about this error to GitHub so we can help you fi Latin - CJK - CJK + CJK (Chinese/Japanese/Korean) + CJK (Chinese/Japanese/Korean) Cyrillic @@ -1775,6 +1775,255 @@ Tap and hold to arrange. Vietnamese Vietnamese + + {0} Font Families, {1} Font Faces installed on your computer + {0} Font Families, {1} Font Faces installed on your computer + + + Installed Fonts + Installed Fonts + + + Export installed fonts + Export installed fonts + + + By Design Language + By Design Language + + + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. + + + Close + Close + + + Install Font + Install Font + + + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Please read all of instructions before continuing to install your font + +1. Press "Start" below +2. Select "Windows Font Viewer" and choose "Just once" or "open" +3. Press "Install" in the top of the font viewer Window + +Your web font will be converted to OTF format for installation. + Word in such a way that encourages the user to read all the steps before continuing + + + Install + Install + + + By Unicode Range + By Unicode Range + + + Advanced + Advanced + + + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + If enabled, hides deprecated icons in Segoe MDL2 and Segoe Fluent Icon fonts by default. Icons can still be shown using the filter flyout. + +Referencing deprecated icons by unicode codepoint is not recommended, and all deprecated icons have non-deprecated equivalents. + + + Hide deprecated icons + Hide deprecated icons + + + Start + Start + + + Hex Value + Hex Value + + + Preview File + Preview File + + + {0} Font Families, {1} Font Faces imported + {0} Font Families, {1} Font Faces imported + + + Include version number in file name + Include version number in file name + + + Hide Unihan data + Hide Unihan data + + + Show Unihan data + Show Unihan data + + + Definition + Definition + + + Pronunciations + Pronunciations + + + The most customary jyutping (Cantonese) reading for this ideograph. + The most customary jyutping (Cantonese) reading for this ideograph. + + + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + + + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> + + + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). + + + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + + + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. + + + The Sino-Japanese pronunciation(s) of this ideograph. + The Sino-Japanese pronunciation(s) of this ideograph. + + + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + The Korean pronunciation(s) of this ideograph, using the Yale romanization system. + + + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. + + + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). + +Mandarin readings are in hànyǔ pīnyīn. Cantonese readings are in jyutping. Note that some ideographs have readings which would ordinarily be considered invalid, such as polysyllabic readings. + +If an ideograph has multiple entries, it means that the ideograph has multiple definitions and the readings are grouped in order of those definitions. + + + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + The Tang dynasty pronunciation(s) of this ideograph, derived from or consistent with T’ang Poetic Vocabulary by Hugh M. Stimson, Far Eastern Publications, Yale University 1976. + + + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + One or more Hànyǔ Pīnyīn readings as given in Tōngyòng Guīfàn Hànzì Zìdiǎn. + + + The ideograph’s pronunciation(s) in Quốc ngữ. + The ideograph’s pronunciation(s) in Quốc ngữ. + + + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + One or more Hànyǔ Pīnyīn readings as given in the Xiàndài Hànyǔ Cídiǎn + + + Cantonese + Cantonese + + + Hangul + Hangul + + + Hanyu Pinlu + Hanyu Pinlu + + + Hànyǔ Pīnyīn + Hànyǔ Pīnyīn + + + Japanese + Japanese + + + Japanese Kun + Japanese Kun + + + Japanese On + Japanese On + + + Korean + Korean + + + Mandarin + Mandarin + + + SmSZ + SmSZ + + + Tang + Tang + + + Hànyǔ Pīnyīn (TGHZ) + Hànyǔ Pīnyīn (TGHZ) + + + Vietnamese + Vietnamese + + + Hànyǔ Pīnyīn (XHC) + Hànyǔ Pīnyīn (XHC) + + + HIDDEN + HIDDEN + + + Chinese (Simplified) + Chinese (Simplified) + + + The selected character is currently hidden by your selected filters + The selected character is currently hidden by your selected filters + diff --git a/CharacterMap/CharacterMap/Services/GlyphService.cs b/CharacterMap/CharacterMap/Services/GlyphService.cs index 0b92693b..97b62b30 100644 --- a/CharacterMap/CharacterMap/Services/GlyphService.cs +++ b/CharacterMap/CharacterMap/Services/GlyphService.cs @@ -134,6 +134,7 @@ internal static Task> SearchAsync(string query, FontVa S("Hebrew", "עטלף אבק נס דרך מזגן שהתפוצץ כי חם", false), // Hebrew S("Arabic", "نص حكيم له سر قاطع وذو شأن عظيم مكتوب على ثوب أخضر ومغلف بجلد أزرق", false), // Arabic, S("Korean", "키스의 고유조건은 입술끼리 만나야 하고 특별한 기술은 필요치 않다"), // Korean + S("ChineseSimplified", "床前明月光 疑是地上霜 举头望明月 低头思故乡"), // Chinese (Simplified) S("ChineseTraditional", "視野無限廣,窗外有藍天"), // Chinese (Traditional) S("Japanese", "いろはにほへと ちりぬるを わかよたれそ つねならむ うゐのおくやま けふこえて あさきゆめみし ゑひもせす(ん)") // Japanese }; diff --git a/CharacterMap/CharacterMap/Strings/en-US/Resources.resw b/CharacterMap/CharacterMap/Strings/en-US/Resources.resw index 8e81dd53..a98fc4da 100644 --- a/CharacterMap/CharacterMap/Strings/en-US/Resources.resw +++ b/CharacterMap/CharacterMap/Strings/en-US/Resources.resw @@ -829,7 +829,7 @@ Please consider posting details about this error to GitHub so we can help you fi Latin - CJK + CJK (Chinese/Japanese/Korean) Cyrillic @@ -1445,7 +1445,7 @@ Tap and hold to arrange. Export installed fonts - Design Language + By Design Language Deprecated icon glyphs are hidden by default based on your settings. You can show them using the filter button above. @@ -1470,7 +1470,7 @@ Your web font will be converted to OTF format for installation. Install - Unicode Range + By Unicode Range Advanced @@ -1523,7 +1523,9 @@ Referencing deprecated icons by unicode codepoint is not recommended, and all de The 漢語拼音 Hànyǔ Pīnyīn reading(s) appearing in the edition of 《漢語大字典》 Hànyǔ Dà Zìdiǎn (HDZ) specified in the kHanYu property description (q.v.). - The Japanese readings(s) for this ideograph expressed in Kana. Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). + The Japanese readings(s) for this ideograph expressed in Kana. + +Readings expressed in Hiragana are generally considered Kun-yomi (訓読み), and readings expressed in Katakana are generally considered On-yomi (音読み). The Japanese pronunciation(s) of this ideograph in the Hepburn romanization. @@ -1535,7 +1537,11 @@ Referencing deprecated icons by unicode codepoint is not recommended, and all de The Korean pronunciation(s) of this ideograph, using the Yale romanization system. - The most customary pīnyīn reading for this ideograph. When there are two values, then the first is preferred for zh-Hans (CN) and the second is preferred for zh-Hant (TW). When there is only one value, it is appropriate for both. + The most customary pīnyīn reading for this ideograph. + +When there are two values, then the first is preferred for Simplified Chinese (zh-Hans (CN)) and the second is preferred for Traditional Chinese (zh-Hant (TW)). + +When there is only one value, it is appropriate for both. This represents the Mandarin and Cantonese readings(s) of the ideograph in the Soengmou San Zidin (商務 新字典, New Commercial Press Character Dictionary). @@ -1598,4 +1604,13 @@ If an ideograph has multiple entries, it means that the ideograph has multiple d Hànyǔ Pīnyīn (XHC) + + HIDDEN + + + The selected character is currently hidden by your selected filters + + + Chinese (Simplified) + \ No newline at end of file diff --git a/CharacterMap/CharacterMap/Strings/zh-CN/Resources.resw b/CharacterMap/CharacterMap/Strings/zh-CN/Resources.resw index d1a0e267..d4fbca4d 100644 --- a/CharacterMap/CharacterMap/Strings/zh-CN/Resources.resw +++ b/CharacterMap/CharacterMap/Strings/zh-CN/Resources.resw @@ -657,6 +657,9 @@ 泰语 + + 有朋自远方来 不亦乐乎 + 切换到字符集视图 @@ -716,4 +719,10 @@ 该应用使用了以下的开源库 + + 简体中文 + + + 简体中文 + \ No newline at end of file diff --git a/CharacterMap/CharacterMap/Themes/FluentThemeStyles.xaml b/CharacterMap/CharacterMap/Themes/FluentThemeStyles.xaml index 6393596e..46fc5e94 100644 --- a/CharacterMap/CharacterMap/Themes/FluentThemeStyles.xaml +++ b/CharacterMap/CharacterMap/Themes/FluentThemeStyles.xaml @@ -1224,8 +1224,6 @@ diff --git a/CharacterMap/CharacterMap/Themes/Generic.xaml b/CharacterMap/CharacterMap/Themes/Generic.xaml index e94e4d61..df4b400e 100644 --- a/CharacterMap/CharacterMap/Themes/Generic.xaml +++ b/CharacterMap/CharacterMap/Themes/Generic.xaml @@ -249,27 +249,28 @@ VerticalContentAlignment="Center" TextWrapping="WrapWholeWords" /> - + CornerRadius="8" + Foreground="White" + Style="{StaticResource MapInfoButtonStyle}"> - + diff --git a/CharacterMap/CharacterMap/ViewModels/FontMapViewModel.cs b/CharacterMap/CharacterMap/ViewModels/FontMapViewModel.cs index 7b5756ae..4dea2fe6 100644 --- a/CharacterMap/CharacterMap/ViewModels/FontMapViewModel.cs +++ b/CharacterMap/CharacterMap/ViewModels/FontMapViewModel.cs @@ -570,10 +570,13 @@ internal async void Search(string query) if (results.Count == 0) results = null; + // If we have filtered out any characters from the character list we should + // group the search results into characters that are shown and characters + // that are hidden by filtering if (results != null && SelectedGlyphCategories.Any(c => c.IsSelected is false)) { - var groups = SearchResultsGroup.CreateGroups(results, SelectedGlyphCategories); - if (groups[1].Count > 0) + if (SearchResultsGroup.CreateGroups(results, SelectedGlyphCategories) is { } groups + && groups.HasHiddenResults) { IsSearchGrouped = true; SearchResults = groups; diff --git a/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs b/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs index cff8ad65..b7a84ec6 100644 --- a/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs +++ b/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs @@ -590,7 +590,7 @@ public void OpenCalligraphy() public void SelectCharacter(Character ch) { - if (null != ch) + if (ch is not null) { CharGrid.SelectedItem = ch; CharGrid.ScrollIntoView(ch); @@ -728,10 +728,13 @@ internal void SearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextCha internal void SearchBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) { - if (args.SelectedItem is IGlyphData data - && ViewModel.Chars.FirstOrDefault(c => c.UnicodeIndex == data.UnicodeIndex) is Character c) + if (args.SelectedItem is IGlyphData data) { - SelectCharacter(c); + if (ViewModel.Chars.FirstOrDefault(c => c.UnicodeIndex == data.UnicodeIndex) is Character c) + SelectCharacter(c); + else + ViewModel.Messenger.Send(new AppNotificationMessage(true, + Localization.Get("NotificationSearchSelectedCharHidden"))); } } From 45a1db495d8aa7b1df2c9272d37b2ea21b4db020 Mon Sep 17 00:00:00 2001 From: Johnny Westlake Date: Fri, 3 Nov 2023 14:21:01 +0000 Subject: [PATCH 24/31] Improve formatting of Unihan data and ignore useless Unicode data --- .../CharacterMap/Assets/Data/GlyphData.db | 4 +- .../CharacterMap.de-DE.xlf | 4 +- .../CharacterMap.en-GB.xlf | 4 +- .../CharacterMap.fr-FR.xlf | 4 +- .../CharacterMap.it-IT.xlf | 4 +- .../CharacterMap.pl-PL.xlf | 4 +- .../CharacterMap.ru-RU.xlf | 4 +- .../MultilingualResources/CharacterMap.ta.xlf | 4 +- .../CharacterMap.th-TH.xlf | 4 +- .../CharacterMap.zh-CN.xlf | 4 +- .../CharacterMap.zh-TW.xlf | 4 +- .../Provider/SQLiteGlyphProvider.Debug.cs | 61 ++++++++++++++++++- .../CharacterMap/Strings/en-US/Resources.resw | 2 +- 13 files changed, 81 insertions(+), 26 deletions(-) diff --git a/CharacterMap/CharacterMap/Assets/Data/GlyphData.db b/CharacterMap/CharacterMap/Assets/Data/GlyphData.db index c985af1f..95fc1cd4 100644 --- a/CharacterMap/CharacterMap/Assets/Data/GlyphData.db +++ b/CharacterMap/CharacterMap/Assets/Data/GlyphData.db @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a3dfa6c6c3113b03733149a3bad291ab462be1c6efbb13e9659588f579b7343 -size 12656640 +oid sha256:6921d763ad5bb910ebf3ca5614e5ba7bb51637f58d91a080b8fff4f3fc9951a3 +size 12726272 diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.de-DE.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.de-DE.xlf index 7325c813..30349f04 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.de-DE.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.de-DE.xlf @@ -1881,8 +1881,8 @@ Referencing deprecated icons by unicode codepoint is not recommended, and all de The most customary jyutping (Cantonese) reading for this ideograph. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.en-GB.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.en-GB.xlf index 3d88200f..1d993666 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.en-GB.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.en-GB.xlf @@ -1880,8 +1880,8 @@ Referencing deprecated icons by unicode codepoint is not recommended, and all de The most customary jyutping (Cantonese) reading for this ideograph. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.fr-FR.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.fr-FR.xlf index 1fece5b2..f1774166 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.fr-FR.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.fr-FR.xlf @@ -1882,8 +1882,8 @@ Referencing deprecated icons by unicode codepoint is not recommended, and all de The most customary jyutping (Cantonese) reading for this ideograph. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.it-IT.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.it-IT.xlf index 5a0e4fe4..20fc37a4 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.it-IT.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.it-IT.xlf @@ -1886,8 +1886,8 @@ Referencing deprecated icons by unicode codepoint is not recommended, and all de The most customary jyutping (Cantonese) reading for this ideograph. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.pl-PL.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.pl-PL.xlf index 37bdd57f..63f2dd46 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.pl-PL.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.pl-PL.xlf @@ -1884,8 +1884,8 @@ Referencing deprecated icons by unicode codepoint is not recommended, and all de The most customary jyutping (Cantonese) reading for this ideograph. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ru-RU.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ru-RU.xlf index 6561494b..86a2ae0e 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ru-RU.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ru-RU.xlf @@ -1882,8 +1882,8 @@ Referencing deprecated icons by unicode codepoint is not recommended, and all de The most customary jyutping (Cantonese) reading for this ideograph. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ta.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ta.xlf index 550f4ddf..98e478ed 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ta.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ta.xlf @@ -1894,8 +1894,8 @@ Referencing deprecated icons by unicode codepoint is not recommended, and all de The most customary jyutping (Cantonese) reading for this ideograph. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.th-TH.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.th-TH.xlf index d9544935..be71a18a 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.th-TH.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.th-TH.xlf @@ -1889,8 +1889,8 @@ Referencing deprecated icons by unicode codepoint is not recommended, and all de The most customary jyutping (Cantonese) reading for this ideograph. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-CN.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-CN.xlf index c74943d0..7c1aabae 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-CN.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-CN.xlf @@ -1896,8 +1896,8 @@ Referencing deprecated icons by unicode codepoint is not recommended, and all de The most customary jyutping (Cantonese) reading for this ideograph. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-TW.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-TW.xlf index db18e280..b578b09d 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-TW.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-TW.xlf @@ -1885,8 +1885,8 @@ Referencing deprecated icons by unicode codepoint is not recommended, and all de The most customary jyutping (Cantonese) reading for this ideograph. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> diff --git a/CharacterMap/CharacterMap/Provider/SQLiteGlyphProvider.Debug.cs b/CharacterMap/CharacterMap/Provider/SQLiteGlyphProvider.Debug.cs index 752a51ef..e216e35e 100644 --- a/CharacterMap/CharacterMap/Provider/SQLiteGlyphProvider.Debug.cs +++ b/CharacterMap/CharacterMap/Provider/SQLiteGlyphProvider.Debug.cs @@ -221,10 +221,57 @@ private Task PopulateUnihanReadingsAsync(SQLiteConnectionString connection) parts = line.Split("\t", StringSplitOptions.None); if (parts.Length == 3) { + var desc = parts[2]; + + if (parts[1] != "kDefinition" && desc.Contains(":")) + { + if (parts[1] == "kHangul") + { + // e.g. 양:0E -> 양 (0E) + desc = $"{desc.Replace(":", " (")})"; + } + else + { + // Special parsing for for Hanyu readings + // e.g. converts '31914.110:yáng' + // to 'yáng (31914.110)' + // + // converts '0069.080:biāo 1008.081:sháo' + // to 'biāo (0069.080), sháo (1008.081)' + + string o = desc; + desc = string.Empty; + + if (o.Contains(" ")) + { + var op = o.Split(" "); + foreach (var opart in op) + Append(opart.Split(":")); + } + else + Append(o.Split(":")); + + void Append(string[] p) + { + if (!string.IsNullOrWhiteSpace(desc)) + desc += ", "; + desc += $"{p[1]} ({p[0]})"; + } + } + } + else if (desc.IndexOf("(") is int idx && idx > 0 && desc[idx - 1] != ' ') + { + // Adds a space infront of brackets + // e.g. 'yáng(179)' bcomes 'yáng (179)' + desc = desc.Insert(idx, " "); + } + else if (parts[1] != "kDefinition") + desc = desc.ToLower(); + var mapping = new UnihanReading( index: int.Parse(parts[0].Remove(0,2), NumberStyles.HexNumber), type: Enum.Parse(parts[1].Remove(0, 1)), - description: parts[2]); + description: desc.Replace(",", ", ").Replace(" ", " ")); mappings.Add(mapping); } @@ -348,6 +395,15 @@ private Task> PopulateUnicodeAsync(SQLiteConnectionString string desc = parts[1] == ctrl ? parts[10] : parts[1]; if (string.IsNullOrWhiteSpace(desc)) desc = parts[1]; // some controls characters are unlabeled + + // Skip things that announce start/end of ranges + if (desc.EndsWith("First>") || desc.EndsWith("Last>")) + continue; + + // Skip Ideograph labels + if (desc.StartsWith("CJK COMPATIBILITY IDEOGRAPH-")) + continue; + int code = Int32.Parse(hex, System.Globalization.NumberStyles.HexNumber); data.Add(new UnicodeGlyphData { @@ -467,9 +523,8 @@ private static void PrepareDatabase(SQLiteConnection con) foreach (SearchTarget target in SearchTarget.KnownTargets) con.CreateTable(target.TargetType); - // Create Fast-Text-Search tables. - #if USE_FTS + // Create Fast-Text-Search tables. /* MDL2 SEARCH */ CreateSearchTable(con, MDL2_SEARCH_TABLE, nameof(MDL2Glyph)); diff --git a/CharacterMap/CharacterMap/Strings/en-US/Resources.resw b/CharacterMap/CharacterMap/Strings/en-US/Resources.resw index a98fc4da..b374cecb 100644 --- a/CharacterMap/CharacterMap/Strings/en-US/Resources.resw +++ b/CharacterMap/CharacterMap/Strings/en-US/Resources.resw @@ -1514,7 +1514,7 @@ Referencing deprecated icons by unicode codepoint is not recommended, and all de The most customary jyutping (Cantonese) reading for this ideograph. - The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) following a colon. + The modern Korean pronunciation(s) for this ideograph in Hangul, with its source(s) in brackets. The Pronunciations and Frequencies of this ideograph, based in part on those appearing in 《現代漢語頻率詞典》 <Xiandai Hanyu Pinlu Cidian> From 6e7f5a76168b553905927df41e90669cf2b095bc Mon Sep 17 00:00:00 2001 From: Johnny Westlake Date: Sun, 5 Nov 2023 10:58:00 +0000 Subject: [PATCH 25/31] QuickCompare style updates --- .gitattributes | 1 + CharacterMap/CharacterMap/App.xaml | 1 + CharacterMap/CharacterMap/CharacterMap.csproj | 1 + .../CharacterMap/Controls/ContentGroup.cs | 40 +- .../Controls/Toolkit/AdaptiveGridView.cs | 343 ++++++++++++++++++ .../CharacterMap/Core/ExportManager.Font.cs | 53 ++- CharacterMap/CharacterMap/Core/FontVariant.cs | 5 +- .../CharacterMap/Models/UnicodeScriptTags.cs | 57 ++- .../CharacterMap.de-DE.xlf | 4 + .../CharacterMap.en-GB.xlf | 4 + .../CharacterMap.fr-FR.xlf | 4 + .../CharacterMap.it-IT.xlf | 4 + .../CharacterMap.pl-PL.xlf | 4 + .../CharacterMap.ru-RU.xlf | 4 + .../MultilingualResources/CharacterMap.ta.xlf | 4 + .../CharacterMap.th-TH.xlf | 4 + .../CharacterMap.zh-CN.xlf | 4 + .../CharacterMap.zh-TW.xlf | 4 + .../CharacterMap/Strings/en-US/Resources.resw | 3 + CharacterMap/CharacterMap/Styles/Button.xaml | 32 +- .../Themes/FluentThemeStyles.xaml | 84 +++-- CharacterMap/CharacterMap/Themes/Generic.xaml | 1 + .../CharacterMap/Views/FontMapView.xaml | 62 +++- .../CharacterMap/Views/QuickCompareView.xaml | 104 +++--- .../Views/QuickCompareView.xaml.cs | 68 ++-- .../CharacterMap/Views/SettingsView.xaml.cs | 3 +- 26 files changed, 702 insertions(+), 196 deletions(-) create mode 100644 CharacterMap/CharacterMap/Controls/Toolkit/AdaptiveGridView.cs diff --git a/.gitattributes b/.gitattributes index f5a955e6..a84222f3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -59,3 +59,4 @@ #*.RTF diff=astextplain CharacterMap/CharacterMap/Assets/Data/GlyphData.db filter=lfs diff=lfs merge=lfs -text CharacterMap/CharacterMap/Assets/Data/Unihan_Readings.txt filter=lfs diff=lfs merge=lfs -text +0_artifacts/*.png filter=lfs diff=lfs merge=lfs -text diff --git a/CharacterMap/CharacterMap/App.xaml b/CharacterMap/CharacterMap/App.xaml index 3200f01d..8ec93ac1 100644 --- a/CharacterMap/CharacterMap/App.xaml +++ b/CharacterMap/CharacterMap/App.xaml @@ -43,6 +43,7 @@ + diff --git a/CharacterMap/CharacterMap/CharacterMap.csproj b/CharacterMap/CharacterMap/CharacterMap.csproj index 16c83ae2..3c0ca2a0 100644 --- a/CharacterMap/CharacterMap/CharacterMap.csproj +++ b/CharacterMap/CharacterMap/CharacterMap.csproj @@ -216,6 +216,7 @@ + diff --git a/CharacterMap/CharacterMap/Controls/ContentGroup.cs b/CharacterMap/CharacterMap/Controls/ContentGroup.cs index dbeec156..ff4350a1 100644 --- a/CharacterMap/CharacterMap/Controls/ContentGroup.cs +++ b/CharacterMap/CharacterMap/Controls/ContentGroup.cs @@ -1,32 +1,32 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; -namespace CharacterMap.Controls +namespace CharacterMap.Controls; + +public sealed class ContentGroup : ItemsControl { - public sealed class ContentGroup : ItemsControl + public object Text { - public object Text - { - get { return (string)GetValue(TextProperty); } - set { SetValue(TextProperty, value); } - } + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } - public static readonly DependencyProperty TextProperty = - DependencyProperty.Register(nameof(Text), typeof(object), typeof(ContentGroup), new PropertyMetadata(null)); + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register(nameof(Text), typeof(object), typeof(ContentGroup), new PropertyMetadata(null)); - public object SecondaryContent - { - get { return (object)GetValue(SecondaryContentProperty); } - set { SetValue(SecondaryContentProperty, value); } - } + public object SecondaryContent + { + get { return (object)GetValue(SecondaryContentProperty); } + set { SetValue(SecondaryContentProperty, value); } + } - public static readonly DependencyProperty SecondaryContentProperty = - DependencyProperty.Register(nameof(SecondaryContent), typeof(object), typeof(ContentGroup), new PropertyMetadata(null)); + public static readonly DependencyProperty SecondaryContentProperty = + DependencyProperty.Register(nameof(SecondaryContent), typeof(object), typeof(ContentGroup), new PropertyMetadata(null)); - public ContentGroup() - { - this.DefaultStyleKey = typeof(ContentGroup); - } + public ContentGroup() + { + this.DefaultStyleKey = typeof(ContentGroup); } } diff --git a/CharacterMap/CharacterMap/Controls/Toolkit/AdaptiveGridView.cs b/CharacterMap/CharacterMap/Controls/Toolkit/AdaptiveGridView.cs new file mode 100644 index 00000000..3831062e --- /dev/null +++ b/CharacterMap/CharacterMap/Controls/Toolkit/AdaptiveGridView.cs @@ -0,0 +1,343 @@ +using System.Windows.Input; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Data; + +namespace Microsoft.Toolkit.Uwp.UI.Controls; + +/// +/// The AdaptiveGridView control allows to present information within a Grid View perfectly adjusting the +/// total display available space. It reacts to changes in the layout as well as the content so it can adapt +/// to different form factors automatically. +/// +/// +/// The number and the width of items are calculated based on the +/// screen resolution in order to fully leverage the available screen space. The property ItemsHeight define +/// the items fixed height and the property DesiredWidth sets the minimum width for the elements to add a +/// new column. +public class AdaptiveGridView : GridView +{ + private bool _needContainerMarginForLayout; + private Binding _heightBinding; + + #region Properties + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ItemClickCommandProperty = + DependencyProperty.Register(nameof(ItemClickCommand), typeof(ICommand), typeof(AdaptiveGridView), new PropertyMetadata(null)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ItemHeightProperty = + DependencyProperty.Register(nameof(ItemHeight), typeof(double), typeof(AdaptiveGridView), new PropertyMetadata(double.NaN)); + + /// + /// Identifies the dependency property. + /// + private static readonly DependencyProperty ItemWidthProperty = + DependencyProperty.Register(nameof(ItemWidth), typeof(double), typeof(AdaptiveGridView), new PropertyMetadata(double.NaN)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DesiredWidthProperty = + DependencyProperty.Register(nameof(DesiredWidth), typeof(double), typeof(AdaptiveGridView), new PropertyMetadata(double.NaN, DesiredWidthChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty StretchContentForSingleRowProperty = + DependencyProperty.Register(nameof(StretchContentForSingleRow), typeof(bool), typeof(AdaptiveGridView), new PropertyMetadata(false, OnStretchContentForSingleRowPropertyChanged)); + + private static void DesiredWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var self = d as AdaptiveGridView; + self.RecalculateLayout(self.ActualWidth); + } + + private static void OnStretchContentForSingleRowPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var self = d as AdaptiveGridView; + self.RecalculateLayout(self.ActualWidth); + } + + /// + /// Gets or sets the desired width of each item + /// + /// The width of the desired. + public double DesiredWidth + { + get { return (double)GetValue(DesiredWidthProperty); } + set { SetValue(DesiredWidthProperty, value); } + } + + /// + /// Gets or sets a value indicating whether the control should stretch the content to fill at least one row. + /// + /// + /// If set to true (default) and there is only one row of items, the items will be stretched to fill the complete row. + /// If set to false, items will have their normal size, which means a gap can exist at the end of the row. + /// + /// A value indicating whether the control should stretch the content to fill at least one row. + public bool StretchContentForSingleRow + { + get { return (bool)GetValue(StretchContentForSingleRowProperty); } + set { SetValue(StretchContentForSingleRowProperty, value); } + } + + /// + /// Gets or sets the command to execute when an item is clicked and the IsItemClickEnabled property is true. + /// + /// The item click command. + public ICommand ItemClickCommand + { + get { return (ICommand)GetValue(ItemClickCommandProperty); } + set { SetValue(ItemClickCommandProperty, value); } + } + + /// + /// Gets or sets the height of each item in the grid. + /// + /// The height of the item. + public double ItemHeight + { + get { return (double)GetValue(ItemHeightProperty); } + set { SetValue(ItemHeightProperty, value); } + } + + private double ItemWidth + { + get { return (double)GetValue(ItemWidthProperty); } + set { SetValue(ItemWidthProperty, value); } + } + + private static int CalculateColumns(double containerWidth, double itemWidth) + { + var columns = (int)Math.Round(containerWidth / itemWidth); + if (columns == 0) + { + columns = 1; + } + + return columns; + } + + #endregion + + /// + /// Initializes a new instance of the class. + /// + public AdaptiveGridView() + { + IsTabStop = false; + SizeChanged += OnSizeChanged; + ItemClick += OnItemClick; + Items.VectorChanged += ItemsOnVectorChanged; + + _heightBinding = new Binding() + { + Source = this, + Path = new (nameof(ItemHeight)), + }; + + // Prevent issues with higher DPIs and underlying panel. #1803 + UseLayoutRounding = false; + } + + /// + /// Prepares the specified element to display the specified item. + /// + /// The element that's used to display the specified item. + /// The item to display. + protected override void PrepareContainerForItemOverride(DependencyObject obj, object item) + { + base.PrepareContainerForItemOverride(obj, item); + if (obj is FrameworkElement element) + { + element.SetBinding(HeightProperty, _heightBinding); + element.Width = ItemWidth; + } + + if (obj is ContentControl contentControl) + { + contentControl.HorizontalContentAlignment = HorizontalAlignment.Stretch; + contentControl.VerticalContentAlignment = VerticalAlignment.Stretch; + } + + if (_needContainerMarginForLayout) + { + _needContainerMarginForLayout = false; + RecalculateLayout(ActualWidth); + } + } + + void UpdateWidths() + { + if (this.ItemsPanelRoot is null) + return; + + foreach (var item in this.ItemsPanelRoot.Children.OfType()) + item.Width = this.ItemWidth; + } + + /// + /// Calculates the width of the grid items. + /// + /// The width of the container control. + /// The calculated item width. + protected virtual double CalculateItemWidth(double containerWidth) + { + if (double.IsNaN(DesiredWidth)) + { + return DesiredWidth; + } + + var columns = CalculateColumns(containerWidth, DesiredWidth); + + // If there's less items than there's columns, reduce the column count (if requested); + if (Items != null && Items.Count > 0 && Items.Count < columns && StretchContentForSingleRow) + { + columns = Items.Count; + } + + // subtract the margin from the width so we place the correct width for placement + var fallbackThickness = default(Thickness); + var itemMargin = AdaptiveHeightValueConverter.GetItemMargin(this, fallbackThickness); + if (itemMargin == fallbackThickness) + { + // No style explicitly defined, or no items or no container for the items + // We need to get an actual margin for proper layout + _needContainerMarginForLayout = true; + } + + return (containerWidth / columns) - itemMargin.Left - itemMargin.Right; + } + + private void ItemsOnVectorChanged(IObservableVector sender, IVectorChangedEventArgs @event) + { + if (!double.IsNaN(ActualWidth)) + { + // If the item count changes, check if more or less columns needs to be rendered, + // in case we were having fewer items than columns. + RecalculateLayout(ActualWidth); + } + } + + private void OnItemClick(object sender, ItemClickEventArgs e) + { + var cmd = ItemClickCommand; + if (cmd != null) + { + if (cmd.CanExecute(e.ClickedItem)) + { + cmd.Execute(e.ClickedItem); + } + } + } + + private void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + // If we are in center alignment, we only care about relayout if the number of columns we can display changes + // Fixes #1737 + if (HorizontalAlignment != HorizontalAlignment.Stretch) + { + var prevColumns = CalculateColumns(e.PreviousSize.Width, DesiredWidth); + var newColumns = CalculateColumns(e.NewSize.Width, DesiredWidth); + + // If the width of the internal list view changes, check if more or less columns needs to be rendered. + if (prevColumns != newColumns) + { + RecalculateLayout(e.NewSize.Width); + } + } + else if (e.PreviousSize.Width != e.NewSize.Width) + { + // We need to recalculate width as our size changes to adjust internal items. + RecalculateLayout(e.NewSize.Width); + } + } + + private void RecalculateLayout(double containerWidth) + { + var itemsPanel = ItemsPanelRoot as Panel; + var panelMargin = itemsPanel != null ? + itemsPanel.Margin.Left + itemsPanel.Margin.Right : + 0; + var padding = Padding.Left + Padding.Right; + var border = BorderThickness.Left + BorderThickness.Right; + + // width should be the displayable width + containerWidth = containerWidth - padding - panelMargin - border; + if (containerWidth > 0) + { + var newWidth = CalculateItemWidth(containerWidth); + ItemWidth = Math.Floor(newWidth); + UpdateWidths(); + } + } +} + +internal class AdaptiveHeightValueConverter : IValueConverter +{ + private Thickness thickness = new (0, 0, 4, 4); + + public Thickness DefaultItemMargin + { + get { return thickness; } + set { thickness = value; } + } + + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value != null) + { + var gridView = (GridView)parameter; + if (gridView == null) + { + return value; + } + + double.TryParse(value.ToString(), out double height); + + var padding = gridView.Padding; + var margin = GetItemMargin(gridView, DefaultItemMargin); + height = height + margin.Top + margin.Bottom + padding.Top + padding.Bottom; + + return height; + } + + return double.NaN; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + + internal static Thickness GetItemMargin(GridView view, Thickness fallback = default(Thickness)) + { + var setter = view.ItemContainerStyle?.Setters.OfType().FirstOrDefault(s => s.Property == FrameworkElement.MarginProperty); + if (setter != null) + { + return (Thickness)setter.Value; + } + else + { + if (view.Items.Count > 0) + { + var container = (GridViewItem)view.ContainerFromIndex(0); + if (container != null) + { + return container.Margin; + } + } + + // Use the default thickness for a GridViewItem + return fallback; + } + } +} \ No newline at end of file diff --git a/CharacterMap/CharacterMap/Core/ExportManager.Font.cs b/CharacterMap/CharacterMap/Core/ExportManager.Font.cs index 1245d273..214b458a 100644 --- a/CharacterMap/CharacterMap/Core/ExportManager.Font.cs +++ b/CharacterMap/CharacterMap/Core/ExportManager.Font.cs @@ -107,7 +107,7 @@ await Task.Run(async () => bool incVersion = ResourceHelper.AppSettings.FontExportIncludeVersion; int c = 0; - var grouped = GetGrouped(fonts); ; + var grouped = GetGrouped(fonts); foreach (var group in grouped) { string fileName = GetFileName(group, scheme, incVersion); @@ -140,11 +140,21 @@ private static async Task TryWriteToFileAsync(FontVariant font, StorageFil return false; } + private static string GetFileName(FontVariant font, ExportNamingScheme scheme) + { + var group = new List { font }.GroupBy(v => DirectWrite.GetFileName(v.Face)).First(); + return GetFileName(group, scheme, ResourceHelper.AppSettings.FontExportIncludeVersion); + } + private static string GetFileName(IGrouping group, ExportNamingScheme scheme, bool includeVersion) { string fileName = null; - string ext = ".ttf"; + string ext = ".otf"; + // Attempt to get the appropriate extension from the current source + // (The group Key **should** be the windows file name.) + // NOTE: Fonts do not actually need file extensions to work properly + // so this may be null var src = group.Key; if (!string.IsNullOrWhiteSpace(src)) { @@ -153,51 +163,32 @@ private static string GetFileName(IGrouping group, ExportNa ext = strsrc; } + // If using the system file name, set it as the target if (scheme == ExportNamingScheme.System && !string.IsNullOrWhiteSpace(src)) fileName = src; + // Optimized export will export as .otf regardless if (scheme is ExportNamingScheme.Optimised && FontFinder.ImportFormats.Contains(ext.ToLower()) is false) - ext = ".ttf"; + ext = ".otf"; - var font = Utils.GetDefaultVariant(group.ToList()); + // Get the default font for the group + FontVariant font = Utils.GetDefaultVariant(group.ToList()); + // If we don't have a file name, get one from our default font if (string.IsNullOrWhiteSpace(fileName)) fileName = $"{font.TryGetFullName().Trim()}"; else fileName = Utils.Humanise(Path.GetFileNameWithoutExtension(fileName), false); - - if (includeVersion && Utils.TryGetVersion(font, out double version)) + // Include version number if requested + if (scheme == ExportNamingScheme.Optimised + && includeVersion + && Utils.TryGetVersion(font, out double version)) { fileName += $" v{version:0.0########}"; } return $"{fileName}{ext.ToLower()}"; } - - private static string GetFileName(FontVariant font, ExportNamingScheme scheme) - { - string fileName = null; - string ext = ".ttf"; - - var src = DirectWrite.GetFileName(font.Face); - if (!string.IsNullOrWhiteSpace(src)) - { - var strsrc = Path.GetExtension(src); - if (!string.IsNullOrWhiteSpace(strsrc)) - ext = strsrc; - } - - if (scheme == ExportNamingScheme.System && !string.IsNullOrWhiteSpace(src)) - fileName = src; - - if (scheme is ExportNamingScheme.Optimised && FontFinder.ImportFormats.Contains(ext) is false) - ext = ".ttf"; - - if (string.IsNullOrWhiteSpace(fileName)) - fileName = $"{font.TryGetFullName().Trim()}{ext}"; - - return $"{Utils.Humanise(Path.GetFileNameWithoutExtension(fileName), false)}{Path.GetExtension(fileName).ToLower()}"; - } } } diff --git a/CharacterMap/CharacterMap/Core/FontVariant.cs b/CharacterMap/CharacterMap/Core/FontVariant.cs index 5c34bcad..512490d1 100644 --- a/CharacterMap/CharacterMap/Core/FontVariant.cs +++ b/CharacterMap/CharacterMap/Core/FontVariant.cs @@ -250,7 +250,10 @@ private FaceMetadataInfo ReadInfoKey(CanvasFontInformation info) if (infos.Count == 0) return null; - var name = info.Humanise(); + var name = info == CanvasFontInformation.DesignScriptLanguageTag + ? Localization.Get($"CanvasFontInformation{info}") + : info.Humanise(); + var dic = infos.ToDictionary(k => k.Key, k => k.Value); if (infos.TryGetValue(CultureInfo.CurrentCulture.Name, out string value) || infos.TryGetValue("en-us", out value)) diff --git a/CharacterMap/CharacterMap/Models/UnicodeScriptTags.cs b/CharacterMap/CharacterMap/Models/UnicodeScriptTags.cs index 62e233ef..0ca0330a 100644 --- a/CharacterMap/CharacterMap/Models/UnicodeScriptTags.cs +++ b/CharacterMap/CharacterMap/Models/UnicodeScriptTags.cs @@ -1,4 +1,5 @@ using System.Globalization; +using System.Text.RegularExpressions; namespace CharacterMap.Models; @@ -217,17 +218,17 @@ public static string GetName(string tag) if (string.IsNullOrEmpty(tag)) return tag; - tag = PadTag(tag); - if (Scripts.TryGetValue(tag, out string name)) + string padded = PadTag(tag); + if (Scripts.TryGetValue(padded, out string name)) return name; // Attempt to handle language/script variants // e.g. vi-latn would parse as 'latn' and return as 'Latin (Vietnamese)' // e.g. sr-cyrl would parse as 'cyrl' and return as 'Cyrillic (Serbian)' // e.g. Hant-HK would parse as 'hant' and return as 'Han (Traditional) (HK)' - if (IsLangTag(tag)) + if (IsLangTag(padded)) { - var parts = tag.Split('-'); + var parts = padded.Split('-'); var script = parts[0].Length == 4 ? parts[0] : parts[1]; if (Scripts.TryGetValue(script, out name)) @@ -235,7 +236,7 @@ public static string GetName(string tag) // Try convert the ISO 639-1 language tag to full name var lng = parts[0].Length == 4 ? parts[1] : parts[0]; try { - var clt = new CultureInfo(lng); + CultureInfo clt = new(lng); if (clt.EnglishName.StartsWith("Unknown") is false) lng = clt.DisplayName; } catch { } @@ -268,11 +269,16 @@ public static string GetBaseTag(string tag) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string PadTag(string tag) { + // Tags should be 4 characters long - tags that are + // shorter should be padded with extra spaces if (tag is not null && tag.Length > 0 && tag.Length < 4) { + // 1. Pad while (tag.Length < 4) tag = tag + tag[tag.Length - 1]; + // 2. Use any corrections to replace a padded version + // with a specific OpenType tag if (_corrections.TryGetValue(tag, out var cor)) tag = cor; } @@ -280,8 +286,45 @@ private static string PadTag(string tag) return tag; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsLangTag(string scriptTag) => scriptTag.Length == 7 && (scriptTag[2] is '-' || scriptTag[4] == '-'); + private static bool IsLangTag(string scriptTag) + => scriptTag.Length == 7 && (scriptTag[2] is '-' || scriptTag[4] == '-'); + + + #region DEBUG + + // Creates a code list that could be used to convert lang tags + // from Apple fonts into names of countries. + static void Generate() + { + Regex regex = new Regex(@"\b[a-zA-Z]{4}\b"); + var countryCodesMapping = new Dictionary(); + CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures); + + foreach (var culture in cultures) + { + try + { + var region = new RegionInfo(culture.LCID); + countryCodesMapping[region.TwoLetterISORegionName] = region.EnglishName; + } + catch (Exception) + { + + } + } + + System.Diagnostics.Debug.WriteLine(CultureInfo.CurrentCulture.EnglishName); + System.Diagnostics.Debug.WriteLine("var countryCodesMapping = new Dictionary() {"); + foreach (var mapping in countryCodesMapping.OrderBy(mapping => mapping.Key)) + { + System.Diagnostics.Debug.WriteLine(" {{ \"{0}\", \"{1}\" }}", mapping.Key, mapping.Value); + } + + System.Diagnostics.Debug.WriteLine("};"); + } + + + #endregion } diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.de-DE.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.de-DE.xlf index 30349f04..fa675851 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.de-DE.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.de-DE.xlf @@ -2020,6 +2020,10 @@ If an ideograph has multiple entries, it means that the ideograph has multiple d The selected character is currently hidden by your selected filters The selected character is currently hidden by your selected filters + + Designed Languages + Designed Languages + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.en-GB.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.en-GB.xlf index 1d993666..8872462f 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.en-GB.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.en-GB.xlf @@ -2019,6 +2019,10 @@ If an ideograph has multiple entries, it means that the ideograph has multiple d The selected character is currently hidden by your selected filters The selected character is currently hidden by your selected filters + + Designed Languages + Designed Languages + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.fr-FR.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.fr-FR.xlf index f1774166..6707ac2a 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.fr-FR.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.fr-FR.xlf @@ -2021,6 +2021,10 @@ If an ideograph has multiple entries, it means that the ideograph has multiple d The selected character is currently hidden by your selected filters The selected character is currently hidden by your selected filters + + Designed Languages + Designed Languages + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.it-IT.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.it-IT.xlf index 20fc37a4..1e5e1ee2 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.it-IT.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.it-IT.xlf @@ -2025,6 +2025,10 @@ If an ideograph has multiple entries, it means that the ideograph has multiple d The selected character is currently hidden by your selected filters The selected character is currently hidden by your selected filters + + Designed Languages + Designed Languages + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.pl-PL.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.pl-PL.xlf index 63f2dd46..ff3132b1 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.pl-PL.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.pl-PL.xlf @@ -2023,6 +2023,10 @@ If an ideograph has multiple entries, it means that the ideograph has multiple d The selected character is currently hidden by your selected filters The selected character is currently hidden by your selected filters + + Designed Languages + Designed Languages + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ru-RU.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ru-RU.xlf index 86a2ae0e..721dae6a 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ru-RU.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ru-RU.xlf @@ -2021,6 +2021,10 @@ If an ideograph has multiple entries, it means that the ideograph has multiple d The selected character is currently hidden by your selected filters The selected character is currently hidden by your selected filters + + Designed Languages + Designed Languages + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ta.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ta.xlf index 98e478ed..4d69b349 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ta.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.ta.xlf @@ -2033,6 +2033,10 @@ If an ideograph has multiple entries, it means that the ideograph has multiple d The selected character is currently hidden by your selected filters The selected character is currently hidden by your selected filters + + Designed Languages + Designed Languages + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.th-TH.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.th-TH.xlf index be71a18a..9bc7efe3 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.th-TH.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.th-TH.xlf @@ -2028,6 +2028,10 @@ If an ideograph has multiple entries, it means that the ideograph has multiple d The selected character is currently hidden by your selected filters The selected character is currently hidden by your selected filters + + Designed Languages + Designed Languages + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-CN.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-CN.xlf index 7c1aabae..fd61c849 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-CN.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-CN.xlf @@ -2035,6 +2035,10 @@ If an ideograph has multiple entries, it means that the ideograph has multiple d The selected character is currently hidden by your selected filters The selected character is currently hidden by your selected filters + + Designed Languages + Designed Languages + diff --git a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-TW.xlf b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-TW.xlf index b578b09d..660b6a03 100644 --- a/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-TW.xlf +++ b/CharacterMap/CharacterMap/MultilingualResources/CharacterMap.zh-TW.xlf @@ -2024,6 +2024,10 @@ If an ideograph has multiple entries, it means that the ideograph has multiple d The selected character is currently hidden by your selected filters The selected character is currently hidden by your selected filters + + Designed Languages + Designed Languages + diff --git a/CharacterMap/CharacterMap/Strings/en-US/Resources.resw b/CharacterMap/CharacterMap/Strings/en-US/Resources.resw index b374cecb..48d1370e 100644 --- a/CharacterMap/CharacterMap/Strings/en-US/Resources.resw +++ b/CharacterMap/CharacterMap/Strings/en-US/Resources.resw @@ -1613,4 +1613,7 @@ If an ideograph has multiple entries, it means that the ideograph has multiple d Chinese (Simplified) + + Designed Languages + \ No newline at end of file diff --git a/CharacterMap/CharacterMap/Styles/Button.xaml b/CharacterMap/CharacterMap/Styles/Button.xaml index 19260191..8fb35b03 100644 --- a/CharacterMap/CharacterMap/Styles/Button.xaml +++ b/CharacterMap/CharacterMap/Styles/Button.xaml @@ -30,6 +30,7 @@ x:Name="Presenter" Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}" + BackgroundTransition="{StaticResource BackgroundTransition}" CornerRadius="{TemplateBinding CornerRadius}"> - + + + + + - + + + + - - - - - - - - - + + + @@ -119,6 +123,7 @@ x:Name="RootElement" h:CompositionFactory.OpacityDuration="0:0:0.12" Background="{TemplateBinding Background}" + BackgroundTransition="{StaticResource BackgroundTransition}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}"> @@ -448,7 +453,10 @@ - + - + + - + - - - - - - + @@ -881,11 +883,15 @@ - - + + + + + + + - diff --git a/CharacterMap/CharacterMap/Views/QuickCompareView.xaml.cs b/CharacterMap/CharacterMap/Views/QuickCompareView.xaml.cs index 9353ef30..1c2ac4a4 100644 --- a/CharacterMap/CharacterMap/Views/QuickCompareView.xaml.cs +++ b/CharacterMap/CharacterMap/Views/QuickCompareView.xaml.cs @@ -1,16 +1,6 @@ -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.Services; -using CharacterMap.ViewModels; -using CharacterMapCX.Controls; -using CommunityToolkit.Mvvm.Messaging; +using CharacterMapCX.Controls; using Microsoft.Toolkit.Uwp.UI.Controls; using Microsoft.UI.Xaml.Controls; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Windows.Foundation; using Windows.UI.ViewManagement; using Windows.UI.Xaml; @@ -29,7 +19,7 @@ public sealed partial class QuickCompareView : ViewBase, IInAppNotificationPrese public QuickCompareView() : this(new(false)) { } - private NavigationHelper _navHelper { get; } = new NavigationHelper(); + private NavigationHelper _navHelper { get; } = new (); public QuickCompareView(QuickCompareArgs args) { @@ -39,6 +29,8 @@ public QuickCompareView(QuickCompareArgs args) ViewModel.PropertyChanged += ViewModel_PropertyChanged; this.DataContext = this; + VisualStateManager.GoToState(this, GridLayoutState.Name, false); + if (ViewModel.IsQuickCompare) VisualStateManager.GoToState(this, QuickCompareState.Name, false); @@ -364,29 +356,6 @@ private void ViewStates_CurrentStateChanging(object sender, VisualStateChangedE return; var ani = ConnectedAnimationService.GetForCurrentView().GetAnimation("Title"); - //ani.Configuration = new BasicConnectedAnimationConfiguration(); - //var c = this.GetElementVisual().Compositor; - //var offset = c.CreateScalarKeyFrameAnimation(); - - ////CubicBezierEasingFunction ease = c.CreateCubicBezierEasingFunction( - //// new Vector2(0.95f, 0.05f), - //// new Vector2(0.79f, 0.04f)); - - //CubicBezierEasingFunction easeOut = c.CreateCubicBezierEasingFunction( - //new Vector2(0.13f, 1.0f), - //new Vector2(0.49f, 1.0f)); - - //offset.InsertExpressionKeyFrame(0.0f, "StartingValue"); - ////offset.InsertExpressionKeyFrame(0.2f, "StartingValue"); - //offset.InsertExpressionKeyFrame(1, "FinalValue", easeOut); - //offset.Duration = TimeSpan.FromSeconds(0.6); - //offset.DelayTime = TimeSpan.FromSeconds(0.15); - - //ani.SetAnimationComponent(ConnectedAnimationComponent.OffsetX, offset); - //ani.SetAnimationComponent(ConnectedAnimationComponent.OffsetY, offset); - //ani.SetAnimationComponent(ConnectedAnimationComponent.Scale, offset); - //ani.SetAnimationComponent(ConnectedAnimationComponent.CrossFade, offset); - ani.TryStart(DetailsTitleContainer);//, new List { DetailsViewContent }); } } @@ -416,21 +385,36 @@ private void Repeater_ContainerContentChanging(ListViewBase sender, ContainerCon args.RegisterUpdateCallback(1, Repeater_ContainerContentChanging); - if (ResourceHelper.AllowExpensiveAnimation) + if (ResourceHelper.AllowExpensiveAnimation && LayoutModeStates.CurrentState == GridLayoutState) { if (args.InRecycleQueue) - { CompositionFactory.PokeUIElementZIndex(args.ItemContainer); - } else - { - var v = ElementCompositionPreview.GetElementVisual(args.ItemContainer); - v.ImplicitAnimations = CompositionFactory.GetRepositionCollection(v.Compositor); - } + SetAnimation(args.ItemContainer, true); } + else + SetAnimation(args.ItemContainer, false); } } + private void LayoutModeStates_CurrentStateChanging(object sender, VisualStateChangedEventArgs e) + { + if (Repeater.ItemsPanelRoot is null) + return; + + // Attempting to change this in XAML causes crashes. + // We don't won't reposition animations in stack layout + bool enable = e.NewState == GridLayoutState; + foreach (var item in Repeater.ItemsPanelRoot.Children.OfType()) + SetAnimation(item, enable); + } + + void SetAnimation(SelectorItem item, bool enable) + { + var v = ElementCompositionPreview.GetElementVisual(item); + v.ImplicitAnimations = enable ? CompositionFactory.GetRepositionCollection(v.Compositor) : null; + } + diff --git a/CharacterMap/CharacterMap/Views/SettingsView.xaml.cs b/CharacterMap/CharacterMap/Views/SettingsView.xaml.cs index d43b6a83..9776c917 100644 --- a/CharacterMap/CharacterMap/Views/SettingsView.xaml.cs +++ b/CharacterMap/CharacterMap/Views/SettingsView.xaml.cs @@ -14,7 +14,7 @@ public sealed partial class SettingsView : ViewBase public bool IsOpen { get; private set; } [ObservableProperty] - private GridLength _titleBarHeight = new GridLength(32); + private GridLength _titleBarHeight = new (32); public int GridSize { @@ -311,6 +311,7 @@ private List GetChildren(Panel p) void UpdateStyle() { + if (DesignMode) return; //string key = Settings.ApplicationDesignTheme == 0 ? "Default" : "FUI"; //if (Settings.ApplicationDesignTheme == 2) key = "Zune"; //var controls = this.GetDescendants().OfType().Where(e => e is not IThemeableControl && Properties.GetStyleKey(e) is not null).ToList(); From a116822115b6f3b2b995230f5109245c6338eb9b Mon Sep 17 00:00:00 2001 From: Johnny Westlake Date: Mon, 6 Nov 2023 18:31:27 +0000 Subject: [PATCH 26/31] Screenshots --- 0_artifacts/Screen_02.png | 3 +++ 0_artifacts/Screen_03.png | 3 +++ 0_artifacts/Screen_04.png | 3 +++ 0_artifacts/Screen_05.png | 3 +++ 0_artifacts/Screen_06.png | 3 +++ 0_artifacts/main.png | Bin 271873 -> 131 bytes .../Services/ActivationService.cs | 3 +++ 7 files changed, 18 insertions(+) create mode 100644 0_artifacts/Screen_02.png create mode 100644 0_artifacts/Screen_03.png create mode 100644 0_artifacts/Screen_04.png create mode 100644 0_artifacts/Screen_05.png create mode 100644 0_artifacts/Screen_06.png diff --git a/0_artifacts/Screen_02.png b/0_artifacts/Screen_02.png new file mode 100644 index 00000000..46b1bb1e --- /dev/null +++ b/0_artifacts/Screen_02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2e0dedde8c50abd757afb38257cb60f9ded9b8801272004902fdd6b4a11282a +size 100640 diff --git a/0_artifacts/Screen_03.png b/0_artifacts/Screen_03.png new file mode 100644 index 00000000..e2c15e89 --- /dev/null +++ b/0_artifacts/Screen_03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96d34d624c57171ea498dd96fcada4c0450733dc12ed5e081445e065a66d2402 +size 86645 diff --git a/0_artifacts/Screen_04.png b/0_artifacts/Screen_04.png new file mode 100644 index 00000000..ee4d2dd6 --- /dev/null +++ b/0_artifacts/Screen_04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99926ad06709d301d6d63887c6279699c7711ab85320891563cdccf5aae04c18 +size 50168 diff --git a/0_artifacts/Screen_05.png b/0_artifacts/Screen_05.png new file mode 100644 index 00000000..4477dbf5 --- /dev/null +++ b/0_artifacts/Screen_05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09d0c7951b751784d2ed474034afc9d4645027fee6db1efcd9d5b4dd8202dd58 +size 49704 diff --git a/0_artifacts/Screen_06.png b/0_artifacts/Screen_06.png new file mode 100644 index 00000000..d8cfd7a1 --- /dev/null +++ b/0_artifacts/Screen_06.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3eb3554346812bd278c1ddb16dc9c8ea45e8629b3d8a44c6e712094c9bc6d24b +size 230851 diff --git a/0_artifacts/main.png b/0_artifacts/main.png index 6910dfd212ebdcef0daa10e4f336471a312b0dc2..d30a680771e119c77898cd3d568ed7a9b18c88ab 100644 GIT binary patch literal 131 zcmWN`NfN>!5CFhCuiyiQWoB6Nn_*F?Qb`Hs;Oot)4}I3p?BgxlTF2a#dh~UB-sg7N zzG=(jrRGtyI*k~;$l2JE+an~vfLIMOBQfgCgb;l(9(1PagRLAOQY4BF0^8(Wx(Z^k OI*8eSRa$R?2k{5jC?-|_ literal 271873 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

cwu+;t#k0bJ($ZQg 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.V=h8r@EY|ex-Zr=^ujCDDkTr`+VK#-oOPS9;znU$Vq)Uq{W+8sB2MP% zn=1kK7@0IBVHi9-zgjeGxA}4OaSe8}dO1NI9$yGV`IW5+CSNdDVIU{m zO?vwdPS23kqo{o)Tk8ezw}IvT)8H%m77fFfTNL+cgU%Zxjn{x1RUYi+%L8_i+C>R{bxDG%}%V+uqxEpF;wgGRSesh zRf>zOd!6^;{1$S1kRwebem844=-+Ai@^^=;EV?`Q6#h$;(kY*vrb#X`6c`%9h6!f< zjgZx6T5We^iqIZ0Ju81q1vEZa;>OrV1q*y@4mf?{rcZDM?+uqxM)V(06admg`0WQQ zwuJ?@1-6UoKi)Nyct`Of$sARUV+9D%tmUOJ)KN?2XtQ#BWv$3UAK)|IIoVDaFTdIE z*0_%?*_uzc_*3l89nK(i9{nvR5H_*69i2(UNqhsDm%VJVWzBj*MgI>A&_WNY)d{Sh z54*itp@T7if{3EH@q2ytJ_QGi(*+Pyl0u(7DOznAb3B$1Z(L0VR^v75&<(HDlF1AG z%^v!J)ghy%FXg2JFp&XbzC6VTbqtBLSw(3yiMQ(r0g(D_3m(M}uUVhA5+xCyN0aE* zJ6x-Rt|q=4MnJL4T3v%>*Dx~))%Q9;Xlo7v5s{LHW~SE$xyApv@w=^t3CsBlz`Fbv z{{_aU`6VBn!1g8L8M05X7#)TII@mZ;;nAB!0*W0sW(tfGc@qyYOy}CVenI$WUrgB( zl#5&2t(d*KmU(~Xz|HbM;HA5IU>J;YpYOah!&PZ*E@0v}o>C+|lDFWy`xoi;eLn#e z7AY{n2o@5a6!liyq? z!G3Y;e=E`n@0;xM-*Oc>F8beW957n9&oAK#X>QMXJ6r20r6d|@po%rZqufhGJzjOOA&U988IX~eclGZ<#4s#t!Y;t+@}lm1 zGwc498^@6&tHX8aev#(5P7YIhT#HgwO|SooO22X1oN>AvV`QAt1(prc ztM5D^vK{wrkvv1M;MXfrTOU?#qQBujnCf-xuZ%C8q~aI!3_j0q`yCok7N3k|>K$;= zFUG7s!DDSQh^?j&cE}@VSiNTva4VqxQf!F5l=d%`;Bos(E6rdcbM2`98-qX%7c&(& z<>eB1GCQGXoJvm(lj3l${v><}OA+?6fy_&bi$u~BtzHGdh16VWRqS+w$(OuhAA_CD z?vHRg;sh}ia1lq%gSfYHb@gkbE@vih!`~!8Pk$eyMMVd;R2swxf5IVu`20WuO;5%? zw(-W;hn?<=KnFW^IO6r8+t{RZh*3!4ES77Tn*#`Lgxkv@fy*DN0#srM@iSup+gk?i zgKGpVJ*HgYTgjnO&3_ly?5M)L#do#oVs`3yNMaufD@2rfJiM8)x*x`>`0=CG>ty50 zIrN|21{oP;7@JqYZo!xrdXWkWvucxZ`DRr1$7{K0^E)ROH5Ot*>Z ztGU7s>HNbabKlPXXldjQ4#b<~J4PpU8w?z0P2O@az4>**w(-L#gQHsO>b>orZ5ce2 z-F_Ee#;eL+xC{Cqfc9caqh?20Xv87l(8gMy-*@hSaVW0h*5>BdxBCqTesl8s-YxLn6YR6*P)udrZT)d{JTxZF$B~+T<5mj zc{f8`sUE7GO6#0;zT<30$;>aDG-q_}dc?A7-#gk^xSfd_)`iz}sk!{@^@88zQ^n<4 z-EnJ&YZDL0oNPiS-MnWRCoKGSu)W*qo@yvXCSRojL4TqLl)3o{anw{|SflM79hpa* zgma-GVZg*uuz(4(JsxD!7&a+bWraY-Kb{8t8R8KivZoTTa6upt;aDOHq$nznGP;YI z0gkD>>@Nk_p#Ix#QrMhw#2RgDM9`cgb#egepjN@uMC)-BEZHw|FjEy2IwST>k31S( zGN^vWg88rn!+$WgwT9h#&Dh~=*we8^<{+}bGj354Sh!sNv{Tz4DGFHdhNyj>z}XiRi}|A>M00h?9w10VEL+SN_J%^(xZuFYuJW>Y(&cs^9)qAhCW*dt5hSPY`R=*H9s(U8 ziJ|;8&PxbhdYMMo*x8S8bD}iLJkw`lqtZl>m)) zxs}V#gNMroRjs-()H9DpCGJ0~6FZV-FcgZRsIx`nNxHx{m<>4+eqZ()k5cwty28 zGjR!1@};2C3n@wFCQpGiEHnLv%1|C;1n`WOFN<{7pfSgQelzwYL5X`&)JPK-ahLFf zLmQ86OZG%+7eJuehy$M!A4$7Sl?bBW!dMX%GFD1Fd%{s*6kLpOj?7l*7+o-WU49NJ zreVR75@Do>Qn2w*b;Lx|bl7pA5S7sSR~si6r_In%P*CTu8Ml94h>s5DKY1^hf#x?* zR?2n9VJwb{`*z5WY6Z;xbChpK=VTZT@MA3?@jGYkH&x94}O-oRPVr`$f9GC8>8*Z*#PuAqEy@PmVic*Ygdk8{~68F@^8i*q9^p|S+7zP&4a3xH{FaI?7tU2=jd4f9n&fq<2ZFVzC#~Pm zL2I5!eClG2sH#n9V)(IP#6l_^?P3iQOBLeA@=*MpC^fk^voW|KmC==JT_<(D0me{wxlZ?*%X}f8CuuR)82JrMpVF;J25>dwy2mX^5J z?S(pFT6ScEA* zO$d1M#)3GAQ6&PMa&0otA)C;@vuA#t&gE%V=w{y1_?cqvMnOI`qG*O|dIC+g%_JZe zv2O`6bD`PqNI2)L=2D!eFic+=YRqV~=~^os>0bH;}?;)Vf_(>Jnpy> z)(6eYeFa~i9K3WQoUnI4(LOrl5I(;5NxaGj95!DLW7w}7>XChZ9Iwu zWm0fXZpo1^!RZ@eGis@nMamb&TF@&q66O|Lq`S$rqrG@t({LiGDbdj9Zin$5L)E5F zT+H`)NJae*C#{MtWn$RAr@2NbN@HR>dW)vS%XC9{=5c3n;#>1BIr79ohy;-a@PfzH zMoVxx6Vmi75`e=%d)6g}&}`xR9w<16wTnPLic2d>6d% zP{5=U$;iotf2{#&RkYU)fap-Wm+be(@F@Cn0hCnr@*;TV z#L~Q!Bnwy!lz16p0(9QYD<#<9M z$&(|8k?Da!DX!Q}NPA{lgkyLyQuyxBprR6GeeJe-+5?Q#^6|s+N+A4~SCXSiQzDz} z)u{T0^8_gqh2txgk`L%SuwNRV6iIGn)d%o4)j}Rps&W|OESEk8+ujy$BF z@rO1;uzUk>>6j_F)Q9KxM=ckhVsy@8x0u;u$2wFh`bQjCQ7L+}k|0aHD9KMMLzd_< zf3B#$Ki>^8cnt-XX+CN%H_Uc zYc#Ar%U{ga5OxKtiaG|Ud`-bJ&19WesF8+ApVU(^`ilZ8K+kaZE4F*$Q5KI%yrsIy zS{$*j7wU*k>Qe%lG9{_DrjKPkEo|b2mS{@30S2ZC2mS~aAO8{Fe>u|?*^s?Tt-Xtm z=ud($+1(U@Nsq_mBzsu_eJu2T1V4n^#tk3Z+BpzrwjqrhkxNu`h(Iv{QMa&WM zjz-u5V98=+(4C@P6q}tAD@P(-;Tteb&>?39Z1N=H{-p@8LSO57kQY1WC=!(68*_$Eenz3#q*lDLc|K6>bJa4&?%^ zv*JI2i_l>1q1Nn?|LrIGL|+o232fvTk~mAfi*|H&BykbK+dpIXCG*$2b|JqYTTk5izpPeBWt?k(jnqEi4Ib{I3T-@cyai9+|YH|!Cm#; z?NZ3s`~KqhmoCk^ZQqNoAG6o{V-*e*ZgfRAu{`={+O%m-A}JD)ZxWLj1F(@vB#||x zVD^xX8b2!cjS{o5w#q#sOU^BnV<1EX>d0(V|3o(BFviRJsRb5!2S6V$Xhb&!XUc=m z*$j(esqaPXg|Sh0Ch@!rM5r+h2;@`LB>P}TtCmo%$^GrqhWuM$31#$$nT8k7l8U1& zUgK*V-Hc%vV^dxvO8Khr>>=;OH$<*Iz)6@SEWxz(Ymn&en1~ql5?I8K`cl)jpyd`= z65EH7h7o}UF7r0kSNGbkyz>YpIF34q@1cjY9VZ_>B{So~+E$q@!3yMWL!?d5i z5Z?RBjBf%>o=&wV9n8j+8KZT4*DDIDdq4jHwLhB6>l^i!n#Zf}b?72G(4KDS38j4~ zcQSpbxM?Th49NEoGs1HZr9Eel!z8bzM!s+ae&sD1L_(kWEj{&h(}(if>uZU;v9buE zi=ynfTnP#IQrNH);!?LI1CMg8OQ$Qjk#bMW5w9rg!nm|qrvGF&R^T$}HuJqs(RHLt zAKD{rJ}ou)Dq|h{hOe%;{V7Anhu25`@mEzw<5^N{PlvU((1J zZ0p!@5!rhEePF9$un}yo?}Ww8id1y@7G}MU?cw+eQ&1!8yujnT6$&R0B$Yd&{*eOy z1g)J1_f=~c+Qd1FhW_rq&t^`lS!*$(%{gb+zmMj6;wbT{xQSX%q>GCTeL8gSd&sW=TjUbx~Bw(12?Akc5yBBC&Cy$GqFu{mt9W@6=$8 z)AH04J_fxG?9P{K7aiQr+s^`XnY=a{*6Fo?Y|rUt%ZBQg?+eGgqZ#vRa=GQ>$Om*0 zM*Zo=&!Ovqbu4hO(<&O&WlfP7^pv0y#eFKlva!0pURs!vlvb{4<@98Qt29)xQrs2P zZ?x^w@jpjtbzo!rs=6VHDd|dCl`%`@w93$r!cbf3V}b-kxuQ#cnvOEhcgrL(f#Wrd zD;JE)RC-j9RpfUu6&Y1#K~In4^hJ^Q-(-96v|<%l{Jm20kloK{H_ryk{g{NR3&7Rwq(A`A0ek!sTIiipb{u2MT9Y) zF;YDLOBJ7FMXZ&>Fx!{38*%EBD@;a+S2k7QSpkr_{dmU zOX8hwNgrA3*Hw#!`_;kS3c5!@M#(;n7vZe(=g@z!?Du8tz+glSv+%Baf?glfe=2mP zE2pJwz9PHGy^#q)PIxVzId&WLG-?J0+c>8yEc%VO|8Sj<;1bf)xvlRGD*G3Tr7J2+ zQ}nxBcj`Y7-j3fNx8;;b*iZlc1l#?m-K3UuzjE03alc~N)8Ina8Ow8|By_*1`=hUT zR_F2gI~^TeOv6u$S+CRcwxw(s_D9p;+uK`dLq8~xe4Y~n$c$(lGN7;L!bM1Zb<%&G)QftZc`p>^n?qoUAMy zI;G5tWFa~@-DpO_^xRj;iXwb&{E4r*3Rl^UjfA9wrMmrjCY;t}NlIVkPtZLE;=oC}iwe(`vU-Qsx#H zbv-;R0U@HT?J1f0xHzGj>FwOubhF`Z`MXE+=P0ComeWp!(CfwDpV>;7sZsA8RG@jm zQtj91qOeCh&!zh1`z;QVTFQH57Z+~sD6s@-STe+7XOFpfc-faxf5(+AB|%Q-SwLqvLAjV^ZnH0* z?O{QhWQ*PNW_?aLTgYzxc&aYrefkT%_e-Ps)Zt8a^h8rajG6gGx=L@a(ZIBJQyQ~& zqtk&j0hjIgrT$yLNST7m*g=DQcd@ip@<@zdPhKEseK690WDAqv_-> zLw!4aQeEXT{DJcqpS~_>7uuuoUXsQsSq88Bi?vP#2Bnm6zx8VEFCfbuwgufkf?ju8 zhrQJRjp6<{H*2%PSFnXU!FtkHbCOr%;pFCQ$uOVCXYh!HUe`|m0>mQj7S6TsC@=4K zmA2?*ZG@%r76eW`jJH`xK>an)Grjyb3=;e96pq83&>QUjZon10d=90ENg2JF88Fq2 zQLFvxVdDn@9|sYr1hXkW({&`H>To)f$XxkD~4^kSNt_6hj8if^|C9T?XZP0>q52? z@w#<}_w#qYfzQD~y4nhmj=pqz-tutsxt?$U>mn2Z$9}cmc)QzE#BDNJg?$ulGe9@x zCcVQng7vE;8a$n7=owu|iI37Is#nTXHf&FHpAppn%_efcT~)d8BP>pfReWiejBS)1`{dMb-%ZLR%81VP95R`wF8 zcza4}8?4RVk`yDaIym{wJ+Y-XcQ%>2>?Ck7$z%R9*U{6{dqqA6nQaWWIyyQA+N;d_ zH;w-hPTyiSJ^dJb@W&f>nxfiJrvE+1Ns7h^YB;C*rq{+{mZ!C5&f`MB+=Up9O$4!6 z>!k{ERIk5P=QNSjQpZpq_%~=Ot(cy!4ic$ahNBue0K5txwfpgAM>0wEbu!Ila!%4> zt^Qsw$rpFqElm}@?ItB$vY3-{y2l<_CPQztha`F7ZwEhzXeRUZv!1tqhJRN&Ro+o# zc+8Bs))Rw`9}{k;~S2@Cqll9$LZZQ!PcdfP5$(9V^i-Kw>b`VR28Z{4T zVXlY>B{~P8@noO;?~<9a=NkM3ugH$n@78SYUduzlw^@~nFLg2e}oua zn&H4>`qESLD)xM5lkFDXx=Uc|mU+5d(0sF(YVf!|kYO-Miu~`I+S>Rshl%-zKinw} zB+^E#pQKv(>N}2cAalC&`pw?paGMciNxN7^_eccd`gAyqU3#XUL;*=5+j<^7mH5~3 zznuM=woU^8_7lxVZh`{5oby~&S$_Xq8cWHs*%Xo+>7xkbf17F>#2ktb_u~=>dM#Gf z^*&4)3W?09B49J)b6Qg#P`D5XRZBL+-xZ338I6RKY&*Q)?;5e!+sd)Uy>$x=lT1RS zCM5;6P|QrsPAJ;0%yP`gBvT7M%@6Ue@&4#}e+Xk)HkPcktDDLHyy%)jKHH%w-Aqb! zocc>w`S+AYq0{eI$>!$f)R;Ki3w=9_T*nPU67&T9V0yhrh!PQ2|$2tG#XS z>LX%(@5cY$4Vl9>?+_J4Pq!2oOOL;BXDZKQAJK!#4or}-$ENS5716@As7#dkKP&*! z3xvX#2999^B)MlBgOY0c$wt+<+!&_E7ebdq9{wg|x7uJo&GGC{6djuI&eXWKUS2gn z9*(^qmP<>21~=9Dcj&IM8yYES@E9Q#ybj*@Q9 z!td5tYQrDED9F>o(h$Ca=Dnh^ox@*!_~CZ7(YaJh&`_h*%0|}u=iSftgdW4zM#dE- z?gx{%0UTYh*^P+h_Elc_lBS4*dPuMYrZSSt-POdo?jx7Yvh92V7#y42 zw#S(9)R{Z^li?0vJkid3RADaxlSh~s=2i;rWJS-L_1zr?g)zqv7 zyEwj(&9-}s6<{|^Kf2EJ2GBPKJde}K?rs{zthI)EfV;4!H;xJbd55#}(QNLcw9e_V zG6hLKTW})d7G-H0g2a6N?U<+Yk4yn7VPQb4`ce~SI_Lhb$=Slt|NWkOhuw3pByuPW z**pfwo|ezscA&F--ySAtcHUlh@J#)^@6|!LTrTD2+GSSTey?`X*erlBn2U-Ex>pxw z@5Iy;0k0D!h;>w>R<9!K`p+aV0~Gmeu|oByVlHU+Dv<(6k2zMbAb7ma$_JTM%j7+6 zM`K#@{gGoT0)$4j+}s|3fryWnJHjr<^U@@~cfaR^R5|GR`3Zb}%PkD6tI!|}wF_*t zW_#Eqk0xnZ`l#bsKx`qo{Nadzlowo%`( zLqoMZiR_`(#fk`910MN;Fsqg`2M3qWRm8OS3v(#E-7H$H*Bae*P%g5I ze=@K4?1wi=h7L(1!yldPD|B3f>V3`=VGhrcjRLP54qy8Sc$(d#MKIljP@te-_=*$N zc*+V23KX(iqx8Pb?{@m}qz{)+l%B2kgv+X5>>*wxYZ19un5O=!{f%@M;Rz!@RBzX1 z^GLj!scGQp`S#=ps`s2qn@Ikle!1g$L^LPx-dscjlve)FrRl`KjD7zrkX&_JZ9AUR zCNBHhY%RU`LU}4}=#No=`S$0n0Bz)`|HGHWw;6s20$9cSAFpMnAVHW%pSuNm^~A|D zA)DpjKY9<|B0pL;?6ZY@U$8Xwu7Am~DCF>*E7RrM+FmT$A~YhzJW zT-F)8wdgP6_h|ZP`)-326)B|tVGc?dReDm5-Rd@528OZ35SDy~#arSUrI`OD=0!WN zfv!=M!HEB)XlJUb#lLQA#6NW~zF^tOKOU>x#1$E=Ty0(*qS;e{*;3DInyL*r{?liD zH(V*J5gKKMy@DFKG&{Id6(^|ryD7=!Wh=3p7Wdprud^V%>1IeTZ~y%kb5B#v)w;qe zlT+}T@CcjOlm=PEm>82Vut{2Lz?jDsRTiFE4WXjWzxo(Y_kEwD((t;++;%LqM&0SJ z3$L{f>^g=}B}J`CU;9zuJwadR{<7}jv=c?RGYS#D?|n!$Nw4r+CWEH$&7hpW!Q+nl z$ry7FOz-DIlJP(BaWWxy*NejL1x=;#7h_!_9Rdx$PjMzaBmZJeO9yqJFdUc`AB)dr zW~PS<$jCT0PDg|ztpfx72z{(#>65uw?ETy&24if!xH9G^>0rf{wGR%a|9<})}Fcd>Q@7UF;XkIuVaawP4aLg9wE zda&0orwF|-FQ?pT%%WF?9wF;zC}5EH>$H9Ycw^L#No??-kRhARcwEz{dyvSA{Z^k= z8y6Lo$;`ba9gMel#LwUb&t6>*KZRI+X5LK6_a)SgGXs zs;Keph#21dbw0CrHs*lU;)ohJ0tgT^w{4?01!_1xGCybI!i8>YY(@M0PKhU~d)w`; zLW&{{s0IoKgFkdE;Kzrb@DUD@@?Wo=iC-Au1n{+d9nZnVhTU<(WrX}oMeUfNzx9x7 zylF&?<~zB9D;-XoEC(Tmp+%`jsv+vnmVY$o{OiwM`?w9WI9h+0kUuq6vh=oU`knZi ze^4w}yfvQX3XoMeVpQEW(U(HYRtxbx+|>@o+juR93~Rq<#s9qgn|{fs*Lo}QJaC(z zf%$vqa(J|gI=agGMy`@Hp6G?R)qlK^Tl2`_^#;mpbT`kKrXEO-$y3Zxj)Zn zi1@=^k!8If&Axv>&^2Iak^5P@g<)b+Bv?jM-wh2CP|mX#>@*{pMq)5)`YwG~xM`}; zbXh*v%m4iE5JOr~9WnI;sJIV&OM%?g{z5Gfz{gv2-bfbUaPRcU*Pmb{Q z)Bc>`=i8>|tz}2Cbga)_jxvOZ z#?gO~OH#!I>=y}W7c!Rjc?B+L)&+il4$e~Jf!9A5R3QuCak=~br zUPpC`4-*Z!R1aSt^&X6k&H102j~X=t>FQWHi44N7PRWst8+8fSrIj5>YCPlm7SG?c`_f9K zZvRqJ7XQ2vY%l5Ct+!5Wrl+=n4 z3niBnvbLxL)f6J?Un+;)RYrWcuOkK>NTafYG{Tod5VP8I^;I?6oig_FVxd5J7*0!U- z&KB;X{nn(cEk)J{2*g5n`YDd4}xNb?HJrKPrzt!J@R4byp^jCBF*Bia(u-v!Jj4}2CwA1>-5d)KIdG$g9f@fw?rC@ zF_%&2%88U3#!Rgp$3b*ee^Sx}wZw)^7N0e3-cmWZ=d)p6K?@3yNFP7s0v-*5c*()Fsd3hJW+biXji)wja zEvCHI-eqh0`xtFs7&Xy@R_o7QW*f48coOiiUfjo#qg%l)qanH=UQEq{7`SD5DgH!RXzuC)i2RJ$bK&$gHTCU5?Vad4JqWcP0I= z0~o`WN}2cKg-JMoUY-qCO#1JdJ_Bt>^Le(x_^UDEY#;|>12Sm7T!gRQFO69pflAb+ zneMk^_6+U~-F!!FKs{M^JM7N{a8VXNA zI-yW93+?_R%Q>Qiv|qHSeD*Y&Of_~jM(}c1IbGTNv~F+}=ROvH0CHG61}nO&)<64D zxysozXm*wySyL_i;gg_7p;1~Av9x@j4^<%}`U}hMV<(p`+IuuUlC2U79KJ{TOEkBi zi>*ei78a;v7`fw0R6+8lTR5yE=$8)f8_vzkj{a_esX?A2Se2>7J#BACL~gqXyd1Al z5TTQ*u^&f60O3WMs>i;`w3WtaEz(?PAua}8*BK$=+d^6$Zsp#X;< z|KqpxZ3th4^#7k$_#K2Eb>0!XjMcPFXWsjFPD>?p(GOOF7c@5BFzL1_bUcrq^V_uY z%0&QqOCM(m|2ljM}8ck8VK9V@5m*kn-B zZ#Rv>2J>k+w6hvWZzck+2ap!Bjb zdEx{OUT9R%vj6^OSfAHZSsK&+B#PNnDu(mh#?Y%OzYEDCtbj&~k-Dro)yNM4Zea9c z0~_}?{*?xl5>fH%NAtV$e-5HyvGzqtm4=>uA>@9cLEHR+x7fEzZKK>?!9s4pT8Lxt z&zg!kfmnxc+K+#}b)gv^4?A z4r)97*S8*(x?WqLb^9?-zJc~lS(uK7+Ez#`QZ-av>=q5Op_&`2w<~DAM zwI(XT&<`X)0{ZXqjhu=%rs)6Cbd^zUcFi_8#odd$TX8GJo#4fxDems>P$c2@l5>a|41cx&d|JY6;4jV@4Hx8Z zv(I_GHqJdj{)8#R5RQGOC-KQpejUyQ1rMZ?B!C~bG{1&aiudT5|N0XdLm6)nxD1b< z3RN;*e5`S%nhd5RSA7V?j3rn!7<{cdo&b~?9Slo%d1Cu1(6id;s2tjuid#lo|MU+V zmq1^}n*0tNnQh2&t=F!Z!o^WBw)5i^5KhpAUNE;`ZdK})uX&M?dQY_m94=FSsr12Z z`fXV!+1)R$NX?{FKl>_rrmeceVnMuAG3Eo{1ucW!YPrnd0I$c}>1}t1hZ8H3l^P|F zbAqD@^QgoMR-yQbwlkJ(7sm0)bHT?l zYsv>h838XCP5%4mKeYG$X-aE?GWj(@7PXS8#iuhXsiFn0C+lJn8tXcVzDp9;!kt7RLf{|P^$glU3{XTrbdZ6(`?rf4Hm z`WBc=^k#6|%JE;yaH6{{vKuk0s$h_KK!vW9%;a5NP%U4x>RXg_HLeRmJbP z|4S(HUtjx)yWjV#1ntT%LiM!a=#T|z5rn(l-OK(v^pKX~55#MYkYoZx;~-~jAqo`n z@o0g#9Td?Zto5X-Ye#jTe;jWC$eOU!M|^?Q+(-UwVwmFg=Tl4S0cG|!^*bm%O(Cn= zapK%*G)N5xh^h(m>2D1E>Y(I5SbGwa(gViS!2qz77^L@D8;}HYIL8oxCokBA-i2@e zRFc=Agb3yqPJENorn2D&3L*K>Q&Q{SkCKMKh)d7L-p?O3JAYY(JvF+dF9O zx7fY&48dr!Vd`;X)x))>O3YUBeR&^^-h0d+#>4+C=uz}X`ql#8tmE8aFCLV1S&bCM zcE&iBq@@~H{r7XWRAI=Fld77Y#@&^4)OEJu&B<$@b@xLo4w+8J`5>FC_w`oFW@FUF zA&xYwC+PJ@n(E9D%)>+50WpYa&NaS;W3X+ z3UgcZxiqr;^*~ZcvtJL26}nmtq=5true~y5Qty8eciB~zTfP36nz-yf?fm&29oFxC zgO8I4AFmG+qA#+;zl_cP@vCXimzy5)S|KAtPCiUek-l-lxiEL1kY+SjT-T>q?iJN5 zx3w)GQX;ziSCa12^@PbSahytJgK^Gq4I~O=jDATFQJ+i|eKa>tUoI&@{Qgyo*sucu zN3mKo$I*!w^yhf^f#1RUQZ;@2W(5WUqf9WN7uBCw0rw2T-7DK`s`#&An&JSlL=JyLZyUdQ4N^giuZ=pm zfPsWP+{Qy)_z{y5pV9C> zr;%Tsb-6{Q3=qqS&oO(eIg7NS75V5Jhuj*!2ab((v^tOImXvcuyIzyE`u}J^`R_I6 znI2Q*8$*Z*QAt<1)ye<6Htr8JrdEhm?oQPuP4>k`r{ctCP$b#oOHrgniWv1Wh>Iu+ zzyA|JHo`@c?sk-oS0RxHY@$dsVXX05$FL5TkFlL+(7PNGs+Crw^w%-Zan}ZMe^<=> zfoyDVYj3-P$BqH?zvVEVfe3K}a)O&MJ__wD$teqeC~0SGh{e4lQa56M#x51s10peG znmP;R_tXQ#K&8?H`Pag&s&W6I<;4M*e)Kv?dspt(OQL(6wA~%5DV)*2uxy$reGmty z)8T`WW*o|`>!Ck$9o~7$M@7c}5#Wi6$@M8Fhv)L(2mJ0~=)SgKrfx!I!(&kc&RQbN z)>S?kbGm#vTEM3cNp%b^ZQ8!eoB8D&imYqjuaXkY!CELbJ4|rnF62x5MFXzy>%Tq$ z6dVl4qkY1ZF4WfIgm{k06t6@=)8h|5mBZa{(IL%T8=nuC>c5aKq*J%Jt0Lx}%P~;% zw)Ub>Yc{zONqHy)fte!{HMGr;l!gcC+=WOrI5Vq!=h(L5RX=rtn=1a6HJ#xxiB{U8 zkS7`o9?6d}F%n_aBEgpfE$aptA;@{oKJUGiNLLr}tO|~?Mi-eg;UsHtIl$k~MN==0 zTu>rhLpVJ1^13=I=h@&Q7_q5Jx9l$AUjbu8{N@Xz5eD~c8uzxqUVok~v!{1rum3?A zQ=Ur1hE~k31DRf_-8RMIl__2N;BaV{fhgn-A>()0MiqgsDi3Y^&{MtWuWKU}6$ioN>zJMy(tql@C zGm>#E-<`IQ+p~5Hzl^$_Z)_@E3}h3}OBimlvHx~S0#1gw*U1V=Sdwl@N|y!V=DI}~ z@Rhy^Zbda;8aaroEJAhKd1w?&coz#bxvg-@a{RszA#duv0dX((M|fiz<*O8 z8q|S{$Bk?eLqkIoVumD0>xODksR1FBb?R83sje3#@cXfmTs3oPEgV*;27PyttC*UI zV7P+f79jKtK#I3ugMUXBphcTtJ`VYQ0uA_&R%x6fzC0VDk0r1WZ?slpgY2LBgZOJB z#LZWf+}Ql@@K@8-CdtuXWE{rP!~=yOqEL{0KRe5Kl#tT#i1Z}?o~D>LrNZ#JssTJ~ z-qDv16A5w+?HtR3?a(_4b;nR-e-5K)>Zy1jT;n!w?m21cAKe+{fJ^dI22+LSP%GIT zw|s6?gx;xpNy0y+_4U{&12vXH8-uz7`jRY;2|ogqZmUEDbhT4bFkpc8{R4Bg0)w8x z$|NPkW(U#!c70#^j$nNZKVRS*m6#YoiFv@!=!d|JG&GJ)Srysg4d=sld6}40*JfJQ z$xiqcCLEsw6nEzLQvwStZu*UHMGBp?H?6876y&y(n-m%)A{21ld<7^X1$r|gzrl$c zE%-l8h;_H4d-Hz*3f<<3rW#%!BSW!Y?W@TZ=M@QTNUCq|zEn6om^7`jCLIs8BL z?GiO)O86nzQy$)%x9?Bu{?KYjlNz&W!1CmIs!RLoYmJo-1oZkW;C7a|Av22-tLWX) zm5!mRT`Hwqe9SVi`cUN?@d~E27?iw6LP^#3h5Kk55=^O&546_{4%56d3N(kd{87P} zN5n+SQ`S(xJdl_r;2{86%RhKd-Yq7)paF!{EI%C^SidN_P0f(1N?UpE*enGzlO{dA zAA~>CzC!Q$!`79OVNzI#(9rWc+K7nro2>QMVBn7ZAb>X|FKAfc0ElCwUdAi{;T$Ie zTJuJvB6#|(bdcr_X&q4F`)~!HV}o(lwp#igbd8vpFy=T^M*Qi!)h?LA5Sun_cmQ!2 z@Ch!LpSeGV5o!g0h zy#+It&#O-uNNY>6t*?`vB+lUl2}z(_my(VO=EWbis##-Z9P#leh|j9azkb1!`WI2J zGb~MouoU-)NHazTOH_=B0MYR=_-KOJQPhYx*Xl3FkSIv3Qdm&v>^0~Wihl7b#u}%% zyBv1=o(`o7bp-^-+}bCoTR1lXwk4eJfdz$9%CRW^s{?cF_G^a;LpqT$64COVz7WI7 z^Zj(OPfB|n|IY<@B=RJHBIjA{Pc11a>0y#-B@kxXia(Q<+rE1BjDI>zfi%f9eUDkn z;~@Cw{q4nhB||whApHe0uoYCUX|?F>+Eg-Rl|F4g*w-Cgl;_v^elV?$qw3B?5cOF{ zA71^V%vDgh76ZJ~7iLi#ZPHZTz{3(*5^B=gXbe$&4i-bNCxsS=w=>pQdgqi~Q*lC% zuvP@`UxlHQS!{AR!%xP?$N%-wS@(2B(up|$s+|i0^n@EGAaK+dVJnlhCPD13GD(+w z_{KH|1)oBr+XGDkR|w?f*(u{n9dU52>rH9cvtX^GLs$oYzHjO}W?W_1Pbb@y00gP^ z&AuWnv3Yy5rhE%)k*5ay0Xk-Wk{1}5K+U>_!Jf1uPJ()BkZIs|t1+LLV~T z#LN;{(l3@TUS#R4FA#jNua+ezm+I`5>2if-U4{>~MM7bCAj`~dnDESMdH>>!e;j4K z+yqJ^t#0mPSBUHZq&$Nrd|>!3&dlA!dGm-6tUH{^t0$@UqdF1gH2cfh8ZJ{sn{0?f zhskG3<0YQ9*`cAdQ~J@u1~cK|n>)}LG@tg>N4W=PxlU3k*Qdtx>*R1zVRS~b27 zQ*`j{{MV!Eziu0Ng=XXEKb8l-fXSMKhCVnpAY4uu1hfjBo0Y~T16*6u^4Lv1b^9H6 z^J9TDFZ;7=H)1szBZ)7`Q);YYmd-gyiPLCuR+q2aV0p>X1Q>B*?3saq`{b(&tMrv7 z>cDc!^RnAU)yf_AqJnkz63DHh>WR3{N?KT)~OXP(| zSN_-u2k_#Y@fG@sw3wnRO?!7qc=+HZ{qF{(OU$8IG*T_pRIQ!0Zk7-eVDR;54H|U@ z_INMH*LZQ8ug*%8&%GAbX|$b+^b~#d^J_PId?;dP_(v^OEyncP48iP*vH!s(y+@Yd zq`dEdsy}GPWN1|JnzuCD!cobeF8J<#ZJ$65v<93F?EWT@Gh~lx1OP?_4{oZv6eded zZqz9@kk~f1+^e7VI7$1$@y zH%{v z$oTbDrkN_`5s%w)AjF=;^UkiH91)BtHARgB!)eG_hq~Q5^yI1duAO-S3apERv? z|H^<8FMyr&tuYu@cqnP_F8u~cwX_aXX@)?G1eS!pqmuqBO!Be-oAs<>2zG$X7emC{ zwhJ!uU_XBRWW=iy%bGyXWBcJ{nVgy<4h!T&>$;Z!qKL>yq*5vYhaZO-=w*4KYfO3A z<8^MDB*NOrDsX^e-PoE5##gngt1G~N;;=izP|VtA<8fY!dQFDU6T&I=Zp!pl(exQO z#`Is*b!^_h|3&k9S{3I5!FBK}>QpFPqMCX9)WBxk#Q0~`&)q<0YiPn%**D6Fk z)C2`o-5y zZhWlN%ccLaswaN#rx2_%a03fa&???en4&Lz zPEmq_hh1SGB5-r;nPVCZ-ZDMci-yLX7rBoMa4Pp?8g4 zA=VThT6{X#<|Uf=VD{)sf|mO7?9k7A(O>5S2fUw-{yB)nZo{9?G;Ocl5L3$5wa!@- z@eG!e1cte&5`QN3FG2MFos{O41Y&?k>ROfzF*r_I{^#QiZpBXj)AMbO6qA_t7SAhs zIoaHd-!!xf;fgd@-`*gc@th41x*gVlciHKYTK_qh7`dV)WsKy)t&^IgsFYY*|0}$a zPM|mkB1Jhcl2l;*kgvt{;QP-Gzbu!^6oP|l%$El;KJS7dzgfiiI@hKCJ_aZ0A7+_)o&$6mycJ*zYiNJpy|0<1#a5Q{PT_A}%G=_Em&ajQELkh*~O8!x zeX)ioYzo@m+_(#Vsgc@mP8kN&xgQx$wk6&RTfyBrz&X}ZVG-Z_ZJd(iw`FO|8t8Z* z5*#h8K%zQzQYMDrGDMk?6sxKkxSdv@oLE>wo@rUgk2zs#kits@f$ZTqJ|eA47o+F4 z!rZP3ziy5)!b|vGNpHaqq~F-^q>SzbSZ6_O+PTgC0$JzCpG5pW^(={@Yu`ueoq;63O9as)9diiDlbmG|?K~xZD zH=QUopM!G>?hdF3x^`Qt1wfjVHkA&1#uC0JUgM9?;+j|~Th5FB95kpGJSBW1{mc9r zTGhN_hwFV#Hq_{p?vzdz@<2CYiKms95WXU_lcA=9^?Y2^T;G^(!T%c3TP&qi@C~p1 zo9NcduFrbI^V}%*^EsN$`g6JOj^%=VAPSuYfz-)T<6v>hs%EA9ahJb3RT2{nh+pQV z#pxsv6ss?(oXu1$zv=Tl{@jb>I7-)&AJq6{oE{p{>Pv6{s3I<}(?C##wyDPux8(Le zgjyjH*8j5L5b#??+D%I&=83&xY7~_%xX*R=(|R4QzdbQIjp^F;cgx>wHlwKD5RG-u zrL56(Frb~6i@-`Yi9j6LtyqDBHu=StSjACM#or0!urMkS|MbOVGu*H$HQYh6q?CJZ z^(;|GLOLVy37Dh1SJUH+&PuGg&AG5KVYejqrno-4TLohWF%>9IUc25u1rCAAFbpAr z+YXF*UNV_c*xH0BwX)854knDkp;QcUq|&Y%hg|ul&(Lt z){(an#`YL=(IWm-Mzig1`4&!&m|ZoAAe?$lKntKkf<QMl7r*C0?fj90a>VAW6*0pM2 z521yhdUp3!U;Mp8hw0$F8aY<_fcKA1GObH1+7!?*kXO8a(TXqA;k{&?nTDaO4F6D2IqDkU5U z;52#LGb>ew3;=T4oL=`UKN6|w$@-A=p%sYA=;mONK_$=A$EJ1n+V#(aFBtf9hB-7o;HNF1{67>d;#pW!1 zO?(0NxPThyC?8)4kDs)g2=+aNZILvj#HW|KSIW!NlgQAEC%OmOO zBW>`6o!vKM7n9h$MXUP8p^Q`xzAfE;V50MB%v(S41|i9rKy8s_36H9j`ZAC7*v8KhS3b`mpior+8hO_a3Qvgk7`Pp#Wn&!dVhGl5$Kv?g4UlHD8V%9_pJn>qpj+} z$o&)QsSu{52?dUmcBCRf)y2?fq|8m}UWPr0i^NVWmL5}_C08MOH~~(19){eP6o(*A zL<=xbuAdvzT9$zy{+z)CE(fSqT5CGM9TpA$6fXWbXC^BF=usUk5xVnNSV-$|{yhgO z=J~e?P6m4p4Bi_6VG_zGcRlvC23ZMUOAyUv@$%ETEaB_ugVS;$GT)swjzyejj{Q!Ld3;?vAC#=!zZ)^`p zZetWA?i{@ABMgnmbTo1C$uys|w{X?jgJDd#cK_Q1Lk9ufr%Kna%q9uD+}iHKArRCg zE{r=XZbqph>4Sf{$BBf04|4=nB)wP}GFF*sXRn%#=fE|zhh5!;d^C2_AsN#yB~Ll> zXp*%V!=~X<#qoi<>L0Z5Ut49XrB?xEW3hvX|yG$LEakhdf-Ul&f2oDZj zmQ(IC%=8X;jKj#g=sH>o?K3*(U7cfm?rTppszB029lp!1apH}p&n&uM>{iN#di`Uq znKs*}MpasRl+LHiPb#`p18l!{MzHvy{Q_m4sI8c$kT|H(gVm%lVg0p{n8&TsNCUOR z5JNJU^^A}y_TR2Tw)@$1T8z5J831@J2G@Pr26~XE>yaE6xJ>CX0lgSOCi@_!pifm6 zU#S=epxjxjnlWA3>2@NJjmvw)irzR|GT;p_Lb2|yx7srQ6Toq2JtTHmn2`kso|KYSZJmPvd-fBBGdc1> zv;+8>Gx14A?q|zSZr$N$j>)fx;5vJS>Hb~svNr!)q#jvEX^)IQtr6g)K~xy?cg`Uk znb|_QFDL#IKEOk%Y7t7TNqt_t&Wd@b1Gnus?3pJjYYI5_O%=Hc{9!!qxn9oF{73_u zu}SJt+(O#uLhLxGZMpbMxxkE)^uLmS<|hiIu#X5di}@-*;0z9V7*2tJT9P3`nVuZ+ zRqF)|5{!)Ov?5s~!lS12L_6&xV%v(&43TIy?}T|jg~iF`xk_n?IMi`=X>`=6g>vg24iW{VdWug#;03SX%AeXZt+_?S}zG}%>$hA$}K_pT`e|O{O4%PNlbz@1b|Vmqv;K6HrpBRA`4a9}j{<8tFh41N9b1==cbqo|DJ zZA*Y;OT}J>-P+?qAKXoAFH3cyW_QL-soKReMD&OwUb4kL)N;9Qi_TbiT$lh6s=s8q zB|`yB*&$s2`k%s*vD<56z|febqwFJtfU=M=%YUAY^n)tl2;SHh6hUJ65#Qa}zBfEV zL`tijUUW@_#9~t}aepQW{~h!Lx8N;uY`~FkpW?Y>0sth&Us9Cx^@dFuH3w60X4w^Q zqn>(+c-dOIDkjO1DN_h?^@6eI(ddI*73WJDJ^43S6mbQQ`6o2?SYpJ-G z5^wLAD9qIDOj_s-zJlv;e+&>YZMxmE_P|>aC*+D@P3mU2Cr;?y>Vcb}NDxSr)c2wW zer=7&P+U>To$wuuS&e$H>-xgR5DnL8ceFI9{rjmZ!tAy=Wv`a(+mHTUQ%Rj`CM!PG zmf-Y!%dbYZnCy9XbEodnBdjkJRR>-BVdY*1R#~wr@f?Myy7b(*@cDOW zr^!On=}~jbJ%gbEMn{AE03&f4(40#7G$e9OWIhbX5Os z7w=KYz6TrzoHRh(M4vFZX(n4)U`?%88@!4Y+qKNmBoX3{aO9jv^%?q;c`9Wn0x0C; zfsVE^`%P69s#A0{O}c;qK%E#?F^-?~0kdZrL4O8&qFy`#F)v6PUc?GOLSKN2F69 zWy03U_+w!~($MdEJ2J$Ng{c)V&cx^7J($;R`@R17UfLI2YL-3SP&vxv9~(l1i`#Z_ zR>2((PvW}9DZ-W@Y${nz5RMdOX5AU}p*`lBEN<@law!p3kjAdT_gvDiOVKW=6HM|2 zy7H|FiTtjdVbX1SOzilXDHsVWBX|n-Fo~J0i|TX-+Oo2AGs(|u%-2YMFsoie z^ysB&YXZd#me!T~{7m!90%Q)}jOMRQgUy(nwo6`?(ht*USpX598M3ZiZb^(&O2WNqiq<(u-I;olmh#q=cHsQ99w7@xS8y>GUA%JUWrNQDs_ht_R|Nb7dsst@+|9Vf1XZ3Jl!sTMB8L+Vg$A;``_k@2Hs zC{gIwN?|0lGCzl&kjZUD)^qwokuk$#Ms`(d2C5?yK z8h}y`d}P35f1VR`DX!I1C`z^$7cJ3Gk!b?$1yo}B81%?!^vEt@5R6y^%Zm@t#9L+l z{o99~JNn&a#+%L>F~0PaLezH$9qa61+N+v)?02#TrK$_foU%K`adr~5=xLmwYy4tz zvoKCH6dv0(K9}S5_#ZUGgRY>^9oF$og>OK)-1l$4;vfIo}t3~G{*I(PtjEnIrT4A)fE>2C+o=$cOZWnW`+2c)5>(iIIVvk}T@l zY4f_QWiv4GnxhxYAd&dAt5W<>;}YjxoLd=&L{J2+BQS9%qO4a;g{pBRp#2P>#XM@~ z`ADPKpSq79E*F7Y6xsi^AA7+jB+pw8O7M=&fZ32Z`OoQQX0X7cs-<33pAFPuP~LUp zeLDEAa6X~nJ9c?I78!zCGHd3^T1L;v+-_Uk!l2NExQ>2hqw7;U6W&{6ky&CQ4%=J* z-;7SLz?p*ZhGUQg)wc=ATx(Wpk3Hs5*Tl;OxKpv|*~qZkitkkCT(-~s!;}!$93C{F>a(B{9D0?(+tr!dTI;?jnR2G* z66Es}8BYb4H!oz~mtmV9SyfZ~2f)5_eL=nhWISf?=hPKs{Eqh->GH1U<8tA4HzW!? zHUsgnp{+<9e++S&uiZD3*%vGI@`y9db{`M{W|hv3tfS)?gvoaB*TZ$Y4-ODlOJllp zt`8imB-Fj=xS535qx|CF@w&GLEY~{G?RwATNw&7O9#d8tG#(G)8!UHu@d6 z#hp#|zvmqG}eEeSe1aVfL&}F|c^y=H2ov8ia7$ZoI>XaOMBHJMPzhRQ#vS`MiMf`Sylw z(QYl+C-wP8SFqSMFB;y*gL`X|tqG?$wJq?43tfo!0nX*wXRfFSoe(y7vy|f6T>;$G z4bG@_yLboNwM&|Ucw)E?@#xj z$qtcbs7pcuRs=5l@M3TUQiGjjs(Gb$ZXOZn8;&(nS(C!h+tab^K#GM+FEwpe$j=T z{|92g29GfI6^8ud{h`pZRRlEl10W+O7Z3c;|7Vmg8yLjnw66vLM5D@*K?6KIJTe7b zJndGzK6Gx#H0B`+`H!pjm$FDL1K*=3kDGb1bzzs|D8JYIE=nUX5AQl3=WUshbO{rZ zaR#~9%{Gn7Ft`a0u*QBRPLFc*W4FnspF6}=Xe9|wAZZ$P$?+##^1!}nN>LgelAFLe zHC*U?FCuxyrSjxDhG=5a2K3JG*Rc7dXd5OiKTZe>w|_Pak)qDYQL94l?av9_mI9Y> z5e}Bqc)PwI-T89A=>Yz*KXhQpl%!->G^j<(${v)*kkKOQ12#hcY6(#= zSoS33Xkuc#S-s0I#6ne6R0y|2k{RB358=EvMv;FKyA@ktIV0uLLn5kn>2K<$C_yze zqAHVU3ExyZX|WZ|wXz$Rnkict^TGoQdwm9`yTtc&Vk7R|WZq60YYz=4@z1ST@1TI9 zgj8MNK@A+^VV>($L!MA}F~G8urnP|1-L9`zt~(PLT+pHJ8=t&E&_uYHIN8WdT}47s z*wbQR*=`j5$rubIg55jBpDfcr)Gh8YX}KUx+QRlP!i+G1JzH%YpBVjm1NYMt7Nr<1{G|yR$EKXI0Xmnf z^(^H`fGr4UP5)MLx->Hy9;lcFGX|0}4I}0Uy{o5&IXdBWi})Qk9DV+yR`9M~%WZEX z2<>f_b2~}_7afNKOK8=-%Y9GZ&84o+z z!+Ii)d&eJ#4}SR@BtqsrjavE?!F(y1_5UW^4*tHP?odxYUHet9HTe`jJ52uF1R#Lx z40RjSbxPpT4rx5q8pP9u@(SiE?mxvSv&ReS|$jQv^cT?^@7cDlChFcUw z!v)TcVVKqji-FDr03P4Qqb3I4kNeV<=1tG2@w<_j=no(hjZ(qe3p$CQ>#HBxJ6Vhl z)Hr6}gjjJ7HLZgcMj1nG-eTEgrkdA7*&JFB6n>Nq;ljhvH4Ac3vQs70#+eO+G$CT`?%L_ zdK$zNsVV+5f%K&%Hbr#qWi+`%2W3r!g5&|dEOY;~YJ^qf;M+$43$7x*7LDs~UJ=#J zn3B$0Phd*u*W@Ck=|i}($Y|4?xzzG6$DrgG?FREv;fFOA*^<>jxG3pgBv4fhqm7-1 z02*|Gtl7CRfN8mAC8?+{Nguwm&A+`p!4&wd>D+HvPg~rP*eV(e3JL&yXgJt21DWBk!8<=Oi?ZQ=MzYH@O^E8d=(Nk zDk1reoE#D%sTBSKPN8h_3!lHycoK`-#?#FgtM@q$hEIU!yR%~Pd$C~($02I7R{mBj z61m3*-c1tjABb&{1g0Z11ZGV(r3vB%teF$5_r_vq7hJDYzptL<7;L~hYerpQw7dXB zAK@w*xq~Wb$+{8s-{LYuXa@%@1}$4zFQkXlwN##^(-pyZbkh;w5F37lm;&}COm83} zFV}ohC3pvWhmADt7d@;~#>d+k=<3SB>HMP-V`q~|YvOHgoa_C80vD^r@y_q(+a}87 zQ)H~>$Jw1Exn4XZ<1Xl+Y+m4qI$QF06*WB`$#4!zh?g39pfY5 zaG~$ZdQyn|1}wvp&(+>;tjJoWuGtr8wyxLaFfwm3OiOSRuTT3^9cF*PX>0^VaM?;5 zl)`T{lz-M3%Vp04V@+qTuikfUk~jMh8=Oy$bCy(D%X9*78`o!cqw!_Tb1uw~$l&={ z-6x8I0ci4RtvV0E$>4jxqa!o$T)%T+E2~8u^#xp<-QeJwsNgmnV|jg@Oftgt^_7L3 z9Oin3CLWMw!zm7`hIhI-0Yn|e+#dKDIemT9E4u0eZ@!)=--hkda;rr+&vPLqYOvo; z+qXi_!k+Bv@^cbdIZ)1mu6W&?mlmRM+yxVt9w0tj{jAXy{G#f~iY+*PLIx>c=M7p2 zc)aIs$&+)3koHn~)Cf!=H*cNA9Iwwaii|w*a=BKwh!6bj`MTd+ksYbWzojvpL4IDv zKiU=r;#_5}iKF`u-RUkFsWadl978MjgOn?Ho?yrtxp?dIK z<>bad$@)AmsVkQ12SQxL2p>i1cJJMrukhUOC8wnvGMd)YzTDkHCpa6Wn3_-NDH~xT1!Hf$jA_CZev5TM1I0@I5bG znW}s|gm@EgICyF-Y#3Ty-1r+{6v&9jms*4;#EDN7s7TCOi;2q|(zr`v45W#RwFdg{ zXnfN69gi3ok1@{#ggy+30~jC|D9h4ECTM-%KkeKQUEDaSxKY5O9i9Kjd`+0;v7fO% z#kI2ex@h--2Z-?BjHODG#T@^kc}*Z?3EzI!lx{%0xcUv9|M8SdiCDPqJc-zMt=g{g zYsW#C;ihAMtdfWYCGsx2$aQ29$lrQ2{y-|4j|3B)P~hLFsK{W(JUP~MZ+~cp(|&2b z6k0dLw&nS*9Q2Ks)M+Nfq26utET2=;bDFOgdjT4dX;wCnay`dNQ0Z*&vDE$E==3`)wMd~Re@KaLk_(Q$fbv=b(vgh1x}a@L$Fw1)!_3zo&EtGmJ3d|#RJ-N%0UJc9ZHHvC|=A?0;F z9aU3m-TjpP7OcG>Yy0I<7ZJ6=ZS6hG_#$ zF1+XG{k!7V{dO>sjsVzf-#@0y_MRmtGMyB7 zIZa;dbnfYTaU8`~7C6%7`U&vh#zPJ5kfFq($2G?QC*jIP=xzg!x_=G&x`JiQzRuzO z0c;oLDf8S?)Mb2-=^CH9hhP16Vz{7@2)LZlcbh@BR`mI1qj_aU|X>JNdOYOZXnze@cnq<)+=2j+QTfH)$aWDcc$UX(#e+v$c)Y7(LaE#Ccy($61+Ep zYflQw$wxII?q&_s+nXuBu26G;UY!X*t4Sr&p+y$5Zm=TT`tyL@kzb|Z*qZ?Xf-rJ zNWRzcK15uHMq{iZLgqdc0vJV>e{F45_Fc*8j|^Nc&gc;cDZ}m`_ZAkRR*C|5S*pYy7AR`CIRWVCP07sH|7m*pD-K zv;T{4r$*Pd_3m~~SuuW~wMWFZ>uIq#>$~^Ogj0cnsW$m^z2w~54n^C)=REjcZuNG5yj{2h?Anf`T7NDU z){~1jbibcf0}v6j4&$S`uWLK+cTiuOe=1~)IKk)qF6(^2LlykN^|NjW>-Q&em}!YA z22_4dS`e)MQ3L>RFl%Ipi`w_&S2PDsx675YXe#1_zgVuq+ppKy?eMS87Lh~#oc;$+ z+Z|5K7)-lXo{FSjJm>T1-nBQUM@S;`%={Ex7P>cmi4zR50H>|PqPO+&qR$euKLu^*L`mWg@~hPeDrL zRyXBTXXuh(IWZFc*UNmVSR=N3&a9~LY$40rYuxhp%MnU|ky?GH*X<*C(~v~y&>uxe zRmxiNHYJ%6q6gLCDO4iH8PV3&b?TpJ&f*09A5GsJTzA`beS*feZ95GcoY+p|#!h3S z;fWfXjcvQJZ98df<2(2B&Nq|4ay0Y1_Oc_lTW=)GbPG*OjwKG*XQ zv1DMdq$$y<-d$7~u}YIyE;OTGDMT2)!H|12v~$qy?k1q#q11Bdt9L6*CGDMJG=*{g z!)j^r5VcysrxM>#?)ubfyg4#ZtaH{Sdm7J0MjT!v0pke3^}gCsy98z0ObtIWCKG5o4=+v#lIavh8rgF1=@ z6ua77>-RcJ>;9Rx+dwTsyG>%#OYRP00YyF8Sbp0=bS$>R`n~hnp(O%DKwTy5rf zPqs=Seap+s6Pu^TW9((xV@=H(t1{?kK7$}@0G*mGG5jI7#Sc}<*vk?(iK>-}mnmeU zfk8M^<0Ls%mDHbIjb*kRCFjNzi2_S$yfh}H6JO?uO_)cJqi=u?Bg+LW!E>FPZbMdT zN>!y*!2W?LCOe(xi+aq#`^w7J_bS6oryDApbKghSr(r3#s;aX6SsYeRx8v;~G_V5g zheP)&0nQVV^Zapn)%#BOx0{FJ-@@_8z3CYl;x-U&o1PXI+hAjbsF#occ6#0GHD8#S zAdv;YnncSHM>r-yki`xFB08e@9*%YW{cr(n&Tn8}&dK_DK@7G_*HJ2E0y=yM18D~ZBE0Qef#@uPIU+i0I%N&mZr!B zxTw9^@yF{9ht==lHYbbiutmw~nNhRiWo7EpRPSJf(8PVquG#->kNbU^JP*95))Ewk z{Cxiw;=b^Ys)w&j^>i!24TtsZLh zDLTN7tsyK#NY3vO+O4)m{1tWb722WbNhhMZ@TnRry`3j7LKI3$?fgg9M#N-{aY{=q zorngyM1!2q8|C4jIMTx<+&fnNX@BoVtu0M4T>`#Kh+#acn@nC)yX3P$wmkn&KQM2x z(4}|UD_apgqk@Zu+0QW6NF!<}mN#K^cIzGHHg-+DDl!tf!FI1GdC@Kc6Z^J*hc;Eu zA2JEFS-y*D0A*Q>OCj9zSu&OD&BA}7$^?N5yDDaI@)dc|p$QuxXCmV@j&}Q`nL}-X z!Cc_-R)ce~(DQzRt$=qwy3}~H5Eo^Q_@6;BssAEd&Fd<4sC*FNLutA^!&9m5UW9*y_5|p%h?i%FAOwU zIGF29=6B^Jus=ed@(GU3uXn4j7R_5DxR)JsiYa<`)Ih=a!~}n?qoTAF{l5-=_q&M0 zVFkUN-Vdo@J!Z&&Y$0!-oiolBA{)D@Y$0%4_Ix$n8l91ygx_f%R0wv+vzL1CfcMwM z4=`?nxB~m(YVFnt#!lP8rSEVs04wKFMqp^51+cx$xwx#H0hRwMz#`|9bHHx)?4MzV z^X-(m-h<(6N@;=;w5xGZXs8*{koPX<{S&dwRHop+{;h9!S7}E+zX)&dxTO3(p1PSoX=CnK04{Ct19VXkL7crqM0j z+%VgK+pi9tvnzJTfhsj+=hT}%rONPl5?HmJFr`?0@T7ZS!G*1Mcht>lid}HU zoe)AH83vE2$Tm3!Mi!4^O+8doy@+=7h(dHd4e|H~J4y~g(KaqzYpgMO_3~_poK;-{ zt~b4J^kXm#eWu6|pCi4o5o5*UpB7J^MZm8|6fA|Gn}jA3&v{=AQ{@oE@g>#6BhvR; zIvi0c_>}~QAt>;GTr3Y_y2j$l44&f_ex3*(1-3n$_GY##{hnrotom?$NxZ%meXgxp zH>-5QILdR3agOjPav4kc?59h-#S;^k9j}wW-pfpP!Ff3ZN3G6LE1iSszB}OOLgP|2 z0lUv~>|cP$zQo;rW`^_F)XO9I3EW9{!Y%k%eXEnd8C;U(_3SZQzQ@28(qHk~CasN+ ztj0BQ+DXdQ%j}<*S#-u9KY&&i0@iI)w%sH7NEG0Z((Uu!d-ZtmkV47gBLIG~D8v_d z`qnEj?WtrVmaeACFK8#Tj;H*i)k(H*(%j=0?LR1AJnwqF5I9q2NRl1Ivtb?_o{|7p z_G+0!{Hk~dldghT0u=0Dcs#TizbVxj9#^)1K%H&?_se>|eG5Qj-FTC2S0(ESLq50M z7Kalc-~izK>edm7+=d|fmHz~}Efn}D)OEBYw59LF%S-Gsee_R_*x%B_=Dncl?uEIv zSy>AkEEog+xdU^P66u=BLnu09mffJ;Yx!2zlIt5FSU6ja)RYD9?!^=JIj9fB<(Cw`oP`?K3YKz1UD z(^3YWinh!@4kV>HP{@NC{QJq=6H1*mo8ANu=`pk?aD5q(wU#;wSH@Sssg3Od=Kr-% zyWN_sB^Ji|Pp}4E7Z0hJE_<1}Kt?&v)_86nJ?_>ZGvQ8BoeYv9LkPW??0b@crrV+K z@*`yblEF4&i99xheWgQcK8iRsU>)L zlEXKxh?WEEr5aHb-;5*2HbQDI$@LfAJ~K?X>{lz!CGc(5`N+5?!4Tn#aEq!l&O3MY{%YwZkn&&RzFgDXf9{)Zb%@IAICK$g(L&i$yeJl{{U8*ohQ=#=ouZ}BOgb(S(oC^8;VY2 zr8~h=(NRP$p2m3AldXVz7b&4eCnK;&t=C@|cA_uzDPJ+h&jUA!z$enYTtQ4R`0bp4 zkR?Ylduj3>XD0i4W|Pa_&4M?t(}a^GngU;03Zks3oh}|ClFoSUPbor}*7YEv!`Vi0 zm@ROQOJR=dEANjEAz3?lBNm?*?a5qOhnUJyBxCkO0GUZ(g$&^P-~b1*#Lq9Y#Y@)$ z6B82%@CT}p;h@`BuFP2-4AARs;y-cwC_pNmgn)G@y45O^2$kV){&}YAxVXp42~&D3 zDOGp2sEpB^x<&q@%0?zVFMAgwOMn(b4!MeyOA#JJK zp>{kO4Dxr#8)-GnKgBS*96@<)){1H{_~07J;IH>`XG9TKSgiMvC2XBMGZ!)xybc6|LT%#sPU(D7)Js)!kG)4!Is|7gGQW*^zW2(eZCZ zrHv#y0B{u-^H;zjz~-U`;hnx& zvza_l5D>L>u;}n`=*$B5XjIf0JOU=#L49H|Oni*=)RTNB(e5a0Sd-M?mbmB9_lY0# zD-V!gcQ1JN4FAM2*hI}c{(|K8D9oG3g-wtm&`7N8$c3Y@%HkoN#9>LEdAP9F6JazX4@#I zeo+g*AMzMjExEkoW&uI|4D`|+*h(0PfC?gVB}>S!KdlIKla9MP5R}4gLmZI#;`J)_ z`54%w0`OVYR9RWQJy_&ejE-2@xnu4J#g7w_M)#wm=LdRrzeZV^aR|@q08O!#;uFb= zO>XLtmV*tB{NV>;-2oy%4YMoicCtyG^rFR7)bkdT^rF*@rLm+oA9YjZ(Spnf+MH)R zT6JqKPuQxjvV8oMr$8*9T~Yfas1zbHkji!ftw5_dOu=<#DJ;6w%}sJ?oHut7)>VOo zj92<7SvmMF6wWgSJm#F z8VlyLStvaO8Ayjt1cy!}g{i$!+O^IHo~Rg~sL(86A5Bp0!M9Z__f;#@5&~JusXh(= zzyW8+g`CwUr!o@BJ=UPCN82^yHBSEH$4kwfvM-4KuZ598$jcx~l=k!FQ-PZYp6K0b zfeog`dEWJIqaFC1SNUoKZ+AD{2s&f$7}ZiLUE|3oIgBxR*j;k>SfzQ2Bxh*Z7|M3{ z6=F3PZpcErKgTA_bma5#0u>68QBG(D73?>aF^9*gQwUT^xm0#PciWhsY{Q66EYciJ zR}9ug`F=(d``7&e(NQTwF`6=$K-PQpvJLz?QzZ)5pg$a z29RF@Zctx{kTamY=WXu-{i}M9aJT2&8`Pjy*(|&FEZ@v3+v*0||e*TdTmp#Y)q;nFs zl5hV?i@^nZuycn703d3(F@X2c$sQh;l(h)+l0U`2CNf`?o0MgR)bQc{j`g@y`DZ3J zFf0?s1XEilQOwjwmlB0zuc|}x2fGgH$c5QHpkwi;fg}2-O9Dh}0Fa)W&xHv1o?Oc>zvIj;?rH-hcYOT&rpuK# zP*rR7$-mnG)~Ev^^*f*M`F3m5p`v|?dHJlX9lF*iq3J=T*L*$~!f#^^O&up6l*dsD;wOA)oGpVUBt4^W&yD+xy<*exd5M zR11i{rUvw3Xsgx)eS8$mmqezFT4H3M~%cdex7{Z57AbvCX<;VeG zTdS5t5P}KMH(QDA8vm1}l6M4@dhC@aib`-75gD!?Y?CtU!b6ec2eO>bl_M@s7{zD= zIITp^l2lHR=WVZM30R|{_3)trET;=g73vHhcYiSt%Lla%Zw2G%Fy~JuC4`%+WQ=%n znpxn=8ur4y>Mf1B$aCO&QG7i@!J16abtyN!N2ugv_WAqtcXHqxJg zy1#V87z1jyKF<)4%srN^0)F`43Pr-4`B@j@O&DhwLxOh%Nh4oV- zc-W1Q1p7paCbp8bi_o@oef1?`$<@oRFP08IH-|hUhBnM4v(58FV#ROc<2fkNQ}(`4 ztAdvMo-R-Lx*pC0WMz0j*Q4F~#~tU#zuuplQQqF9B4Ig_?O(yoKuvT|pfb|ui-_={}JUWbW2wSa|+6@-8YB|vqX1~BDK0yqa=GkaBxHi2Juh4Jy3+5zi@D;n#9@34YKGj4_RIUy$tGU_7GkkKdg4zsFkPIhTTfSW~3qQ45{4_?V!5#vBPav#CIpzcB)c2QITYTuO=&^Y0otm@D-~ z)p4y*!w11mzpTBYAAOj9HS+BIk>rY3*9QAl7geJ-H6ZS9Vc6+@(pmT=v4LD>rXBJ2 z>7C+NR$=L!mq9QLLTf)ei0OuT_~^%3+_V>cm$Kz6kAJdsAn@(_oV`3wNSPLv>J4+> zI8G9{kSF~XIyiZE^tqVhB!_FHv9Ud}!Ejk7<^S2UOFhM56o1xjWsxI%em0lJ<>+&b zc%*l8k6&)+VL#8l*Y&tD7W27YoWkU5zZ@H+v+4I-;UN6cPg?ks#dLM0c&h!?V1!8U ze*6|X+D_jk=pTKa+1rCZ34zmeE~>BP(4@UIIQ@snwY4|RLFz!IrL`0RKmZY~L8COA zcv8%vM#NXbC4~hPT%jGjFwg4V>}ilm8IsbVZT$dEnzOLJZj#7kgv5`K$F(+4KB^5KnEcejUwa<>>Zf(Lg}ph1BYDj5d2psYpoihURkJ&Rt~Na`1!N#((h4G*^B<3QyY#q(q2b)#>k;iXi@AFRysUz zFf-vnsr(BnaQTAVFU30AV1F!OD`6YmV=b^H?^=JM*3EyS73Tj+s{Mh-;*B)>sO!3R zk;L`nLEs~}Jgcek`6uRSubSs}GR(hXxy)&mO_p?Zov244WaC$8CY-n;6~N>9@@bOr z>f!Wi(kAu{PQX9PZ3R(yHb0WD)ZImAD|us4RA6yqz&MA{MRu68xzUhA%(zBYHpaPs`%t<96%->6w*OL6dEvDh`@Ul>(zfYp2d~{985jDKedam zK~EJ0@3dCl&2deTNt<98ZUB)+VqwjKk{9?+AZR|uPEWb)X+ ztYg$hlmXBaC0QIdYBX9;sf0!g2-MjZ18{$3>@zh()MiVVCMBISDx^!}=qxY;aT#)+ zTqh>sZ%nIwpY>6|^7(Gy2MBXm#d$dvqB=+Lq(1YMG*!ardCrV-WqrRpa6ZLNL7S> z9wus-{%h&l;1~n$<(D1L5duPy`AY1ccEFhKdCl!Niiw6qgJXs6<7|Sz*GWTZfQd1u^FZ`Eisp7Q z&V}KJt!N; zq8dzAonTPkmjb{=hK}YbR3%av3o+&mBKR$Gm_qdnEAe;KC5C-rtLlnlaIU%&R-#Rs ziy~c8c$4KK&7@~&{h;ahoQgvzfalZh6D-c4v{tn)aNk8tIgi=XBOIi}7Q2I?%;=^y zh-U(6x9{Cps;LLR?q}O7Hcub6Q>qdc7AmEy?0O;y&^DpFq?e2!Zmtk>@%hgXTwag6 zaCA0!rl$kq*o;gcn%f>fkT1JX1kR8IK47WNlLuyxMEGCEo~9qy%HK3$?+F!ygx6Lo zmr0CmNY54C(mw<8ydRcrPW~N@HphIs9#W`dU=ZgcYy-E))HO7@cq+dH^yZg=>O5Z3 z3NO1qH<5X_yf$L0x-X=bpn8NJ-@P{4Quy2T!6)tu3rUs>@4X6#80df%$&f1eQ#JKC zoUD5R7z8q6O7G8|hzjFMhagi*vko^bdF!&RCZP`(587o28B`|82fa}dSc$Hnh#Whe7zJG=(ESJnX^A1 zE&~7pLKTX9W9*g~bQYq}=lI*)>lL*HEui?O=;x;@bLNBaTYq zc&80`S{=Y;^14ytG&h6*fWNso+jG;dS zI57uL9I9Vu?rD84I6HTrUy)*33!uKp#b>n36B3iXa9`nWjIMy)+STcpbno# z72rEnONWg@Gl8@js8voAOa0JzK|39I@c1Tzo+cGdED^+xAWgvxvO|fYN%DBh3YU6t zhd!!+qV`%0NZ6L?hz7xEW-+UB=PO0s8VLFpYz8NVhK7TpduNKVmxW7_FDl*?(Se)8 z1Wo67Lo5RLVQc=IrjV;B(VQ6qplHK62Qs@G&Y+wgKrXLnN?5>8CYqS;r2aqk*~U*hP0wkWiHD_;j6LEK)6Hg z`%-ucK2$SGcxsD&CbRSK+i=Y6f4Cqtz}1-FIegQ zT$>o8=O^nqP=cVPbOIkBU(P)Ie9Lcu&Fnc=*hVjG1FRRT_jFK(fZGlD{YreVuWN1O~Z)Y*aoR&0vX7vcbULx zWSjFb@#3Ic3gKEv<7~Ti(12Udq`h-{{XMn2)Aq^dfaj6B;)W2+3+Ki1+ghNC2qABg zbKXJQs!KOdC(7E}<~lR8e+X`cwR66aMiwTn&vl2Gjs_4ti$qo%tM2pZN_cXE@@6rV zi**v=Zu2^csbAy(fDG_qjib;*cTXhN#$lwUxE#r6M|OR=(9=4OFBFJsp}tG9?~Lbakw#7do#PyAl+8>D1D^3ptSzfE z7+!~$1<^>Y_gbknff%G-<4TWA=D?KSAji&unBZ;(f`E-7(01imHa*#0!S$Xp4FlDnS z@tVcmD|iM4J)%~-?*(YCfriT8Z+S47&Z?-N7;f}HrIa(}EI#p1NK$__9DGo8P@mukotnYZ7^Jsyb4(_A~L@s`=cv%{Drk#{cTi#)PPwc&Bgl zowf7na9!DrsBh`dGsoydH{{0n+VI80A%^4#*U*>e9_^ZVo^IQ2Dg~H3>d7V&-?MRR z2@`8!6wJv61`s-8k-zCWTJ=)wOE7*l6=w-b*a)<&baaei4v;iNLU;apRB+&RY&;}0 z3&p)Uy-t~!X(nLKaz?}XFbY&<{uh2hKqrAgcL&v5mdP6c6PpREv@Zk%QTFv7fGnk{ zjPT3qo6eBFs*5Ou4towUif-Z}r|VIAP}8JCfb_u-4~sY6cslRnkTSl@YGK>KkfSy!M8pLRk9is zPr?`roOcIGR``rr=R%>-2&sN6W)wo{htz7-pceEjM zV;8d${Op&zAoCO28bfOp-)yOpWyu4bONSGc3u&&K>i=}j=pc&t;M3)pgZKQPQ)O&H zK1_Tf$c?V3N2s#TW`_ql4!xE4o>NS|zX}|$c??nsj(wUty2OtGHX7QESpIm{T%+#{ zyFo~J@H5*yFFz$S&`kq|A-QI;L}Jmj5Io@^JzuGAHru)Ia-{y?bR$1EaHmB&QR6DX z<-QSM=>)5AYszBSM7guzSxHUc_AyuvQA`LaGF>04Kg?ZoLyB9HW*N-f)aFn?i8pCvzx3YbD{FK|UtAAvq$ z2H%F_Z4{730wMGP5XGcT0ZG>&MZkN>C1&V?lZ&_*#jT{=mo_t87cZBCCObsYAz#FY zi?1HQF!{ni3E*xT3qJFA4JplLYI1*PYv)2stz4IL27f#FvIwPy|L#INqEhf+0lPqC z9H9FOIH*(2Wc8HDYr+QG#5Olmb4ULH*YSua0{>^H(&6x?PuB|6zEUWcbE;c^`Toc% z0t68nt#~^_SzE*K@bHLUfVfaW#o2C$fnN~6yd3NEmxx~j=oB)z5vAjUWfbp0^*4~m@rp)?m& zl%w`*#L1bl3kms(#yI}g>FQYXWB5#3fUTk$>2KVtll^|CZdL7KxaxwxNMp*^+-b^{ z@saci$G4Iyj_bW6P(Z3=ZLi;%&Q|GOIb;0uY6(0RRR%yk{R<-nFgmgWcPA@E`(i>w z6PEi4Ml^ywEc4N$Gxph%RVtBv%&}Ga-3W*~l>SC{Y~E$)Gw>{nsrpAOpGdpi<+NkC zm7fKa9mkfs?5FVO=2a0e|Bz^}+Tk&wlwqo}%V!kqIe#J729^ES38DaC2?mx(V~kS1 zstyfGs>to;&or*eV^Cb}7}lMdM>1x8Geg3QjQ=x*JX+S5kN zgO3Y`!VZx(u1P9&pt_Arv>)S}VzZntq3m{FNa{l&NiO==7bDo;OIeK)lRzJ<_{9z@ zpYg_cib_9rNjOyr9Whs#eg@DZkVC~2T|8!GNVSkZE{?EJ>W1ctP-4of-=b}AvoAW+ zOSH=#0)`7@jH8~u)O63Op|WBIl3;T&uRH;l*kbck@Q+4efW%R$3VOlD4S`2#K7gPsmiNfkZwg&@n@@67~)|6(6IA7xZ`z0A1Fq~y&Z#v*3v zz2kfi;zbHG9Iu1d)h=4rClTd4P9$nAqvB>_JyY&v{y4Cl6oZB_uWKx2D6!?iVTgxd z-d+GJ%cSKmY)!qJq@A`yZR=^ta$(r#n}+$Oe{Z*;O}A{5x8}m%ndBIyI_8M~JFAG} zTq7!S0m(R{VF5muf#QDyotDd`@oe<*=f$>O&ujfOvg|w5wbCH?xbD5*t&CdloemRS z$jH$e8cCDh6d*_(2Rk%lY1O!Wi|f#=dbn1BinQ4~U>_EMCu#}>$A!ggl9~Xg+$pi) zOd=rVjSB;3s)1HkP9CG4Kw(@1`h!(UCbNd&7o_YQmR>Cs9B7@q7m`(BnvmGtM#$^ZCr{_XWHQ|yNvc{Gj|?%RD&*8ome+DGFoS zyUtSZ)pX#V41|EC1^LD0X>G5ovnEOIg?DbKPOWA9Pi_fEE=`(yhhIZ(csY(EK6EcO zixi+beH1G+Z#-g)i5fl<2cjo{$PJ!CIl+JPXpjEcR(Mzy(o=@YDqT#EXLlH9N$G+} zJ|K#mLub?+xgSTUy_wnq8gTUF5+cv~{g92igJV_4>|gCoYh3sMKvmdtS_kSc2Hw;Qq|E?|f zs?m@H9uarvKeSaSA`qTE-{LMV5dVTHlFz!^?%f)!td{Cmp^Pc5eD-i%==p+35-VHT zIM>ZxUSw{haNqN9&H_F576n=C?)ZDl)!#OBaJhV|>owDBH7zQ~d@@sCO`xX9&B*S& zhgvRK)xhbp^Kn6=$F5u66(U^JBJuq>=j_ zV00Muop=4G36W%3<-fJ&CPM|+^SJz&emcoh-L=co;*VwryDdLo@aN|-;<$%#RPYFU z)`R{APs0LkCZT?5ql?PVU>uWST15T0P7x8R6bqDA+GnAk26!Ezv*E40R^T8jfM++8 z6K@gjw^L@&RIQ>my$64CUiid+HkfOuVGYX|Li7DQlm4JrD&f+N%>J6DA|M&^m;@Gt zp;Aspml383N+pwyN-Jm^TZbMIy35{Y1Llomj^dd_VdIt2N;fDvPD_i__)&4ALBj!M zW{pCKWF{-9@F?k{1hH&o6-*b`lMb{QU}kh|`NLWsK70s}Gn!;~@Ic zb9F6nxK;r+!$-jj=du54iNYZP-=hw*p?}B{96C8pH$$#ohXW<>ezuyx{Q<;42;KN=R#nr6zy1rePiWsFtZKhlHRyaWsJ$5&EgLml7jhUFHRCTF^WLhy3?S8ed`~J*uGZ;v^3dNX zEzYLt-?uS*eJNWg*R0S};mxx@j7rPR>V|d&0KSAuOcIKavB7jcZXP%djn+ikLzN0D zNASgLdZ+N;md9fj3Kl!XpL%Y#LeFS?@o>3?{-9i! zoh$!*ym&DbZk2z*9-$u&cNM!MLsctyCm8ZLxMx8Y2c9ENZfd+qrNEDNj}!D!Fqvy) z(psv^=K3V@9K)*hSOUFU#o6wMPel(u5NZ;Nb**y0*0RfaFy$wdL@kGijVf`c#Ya{u z^#7Js5@O|>=)dm>QYHAWWVGh!GIzc_h*Z2%uejjtWLBHW4013>GwCs8&w0@ zE@-xC=(xr;^%c=-Oq#y{ofwB$elhaDU>^Zch8Na}&6~)ONVo|bBAM3<0=+DqbsOJ* zhHJy*g>#=Kfj~-g3BAZ1r0^VcHs|Tw_mMb4j$=wkXfxA)f(QvRl9_znhPQ)~1?%sw zC6wN5?-8@%I=atpzPqop$GDGb`8|SJI$9&ga@c;X`#yd|*hmEpYNznyYKto?RvL63 zJ_uZRy>b5H&`9PP5hf{=Y{CF87)srN&>HnLj-`WNZ>NF-EyW6 zu$}qZ>!1C&M{4Ll$!8&fCFXp%&Sl(x<+wM9rjjGvo{oDvhKEGR{y&AP+nbKT~PrYaH;Q#jW{j*`8>4+ z-uP1K#1IK(A1qWH%xs0S_>#xI+ri?Wfuc<{3rk2F;)#%R2MgSWM~~&?=S!GTF|Kq+ zL6^`C0~JzTZ>s6hZicK&vgXox@!<<|$6y0}>~qEZ#7Z0H(wGz#0s#T`*|EB9786Ij z)kO8Y{@xyER)@L!9qf*#IwhIr2K=uN0++k9McIPS0x5)wa#X%%L9775ScyCp0ZFO{cUyfg}S%>@Rq!meg z(jtH6vxCP?E~Gpycqe9z70Veh{n4-Ees6jYu>t~&Lo4LNvfU?U{x?SiiuP0oPZATt z-dE>;xIQgy?R~#uZvSZAlS=!$rDTX~S12u6JP7`makM#D@+EDG9J;6CANE)pV@GAKK5Ri)FDT@Ja!fqYZg@z7BL*DziDIuncV;;p9Nha1SWpERcrR0?3x;z3;w%zGM{|Z!OF0-6 zi)0$uqs?}i>S#yddoubXag^tuXQu}-F<&jDiP#CfmH%j2_~NE^0k5+Kl+k#hN+6UGOmjxWsG;7poysL zSLG?+?by)>T)~(3HM!CWhz&pnP-=MkXF)(fKr4?&*qQThJ19(3Q)Au#6G}Xc1RB7*;lW6m{ckIs z+EpQq!|J8koMZB7M9JRL(h{oyy=kRi^J02>db_i{tc-x&_$ewa<%Y)dde`Ej#pJ?q zhY|Ea8hK5bXqK3Nqm45mdp!kLyZ6=jH=f98=iOUKhVcJp!hT1px zh&Y1auk6|Du;1Vx-e(R5fq4PHHynq92gsZ{!Po_lJ+CTHiFEP?(Le;M8iLeFsWWN^|Fq*Z~ zL;dlAlAfd;Brw3tj_tXD_Y_WnqSKS9i4-Z8E z4*!op`r<5M|G!WR?=YJ8#LB!cmT~nEJ#&#cr6&S4ZCk>WlBAXJMeLfBccyAA?`b{1 zwc)Ri)6Lc@PsV(_hL)-+^`4<|?R5%R-Gnb0_+~wRJP6pg z-n45iE&6mAo_86(?Q)Imw}GLM&Eov+NC2F&7B5jEB*><;q9T+3pa7jRM)BHH%4^VH4e)Kloq4E zV(O*8!Hm7&rJ;5(ZR?m6jk&%%xnjP2h?Es9y zONxrRkwA}CN;#V`LN8a(M~2$^8_h*UhT`ITcHds<`}<|kh_%$Ef5}cm0Th)m2NXI& zMQWL$jrtfczv{Mpaq3#7LJTl0wqn5diPxZ&r3NvO1aDsevjfB!GKcFt-iP5`pbHvw z`lYt{a3@zx^m=Kw7HJ?wT>^Jgg0v2@KUkKMSiZjYUiDW>nExPl-3m|;kSi#5j2!Pk3!uAgt02KSwz zNn={9{?|4sJ_m_&Q-FY?qN4x2<>Pw$i58QMUgNHuVVCqbO2Oes+;7gyKf)}p<9c%0 zW;Q8zdAlAKUXNI}({$d(F#xG-enbE!Ft>r45^I%VtL^;_ZdrM`qR<`_i5=i+=H8h= zleygRnF0d5UPEt5(nlK z9HZ!P{@kw|f>jmxyAvH}YX^+Y48<&tX%gsw8r&ow!pB>};Y#swI|m93hD&1TBDC9W z5tzlY_PTW?g?8z}nUMhi+HEgnd3^7ff%ICY$qS}ph>g9y+h(Qz=R23e=Y6&RdH1I< z2hFTw7R+#$T!)7w5#P%>ru&-J5NgirNP_Usi7mGKbP0bCATJDrU|clkm{^(NRLs`8{(0E6$ca!=*^7j^~QYJ_Ee{ z*&UW+$vbNN1&>0D8Wptk+*YcOSX*9TxBTBw{kNg}(4~mqlOkE(?w+po9L@uL3A8Go z2Ji3#Je~{d(PjvoKi|H%gVTg5e=kPf06@p%oQ3{syH+);yg*7=VbQ0^#Tcsp&vOeJ z`KOUg!t}+e_lGk%!i@A2N#a)X0S-W_u#INUrthS{;~ChnT3%kidA!AayE5hG;a-U5 zFY^No3$CU}*wFt{-@HHP9x4akn`Zs?VU$GEb?HnM!P!dUlWmB;t8$lcS) z!G_cgaiLO(Y@2@|WrL{YoN$@^HG?In>AC>M2CsP?HO zePM6zduJ$?#e_kz+=jE#RZ;B~mlz-#D+PkNU|#_Mz?1ZtP`8y5t$QS}`m1eXQE4iy z==zAWQ+5Euq44bYPFMFy_^n!=mesLXCl6Lz#eSx=9)+eyJ(CiYfVr0dK~LH(d!{(H z4jCf=rpey~h<#GCZ(pt^9ec@g7>2*VFghODs-OHKS_p$#xg1IG$h;o5K&XgnA&+!# zDF*waeC`T{Z)d}q^AakmCwil|iu2`K)!g^@VCrYV@+$cT390_gPA_jr3O38K@-l#t zf-jV~%y&5<@4Mh(h*vPJ)AY17^kC}AC<8EIIk@`UXKy6K>qGSkZ?0R9FnGRj+9;~@ zcn%f->hb)4M12EvBwf^P$2KRnt%>bqV%xTzi6$M}wrykLOl;el*pt`a|F8F6t?sVs z>f3Aesycn{x##S&_Z=wc)F}jmjcP4a%o7rLOy*Ywt;<&G^aM@B2}Lbx2V#S zx>Aa?s2ZZ?aZ*i6zEpJftY%>~2#8;J3K#!#YdNi_!h#->z$Bf+5`-@wj*5|dZqZ>D zwEf>`T--=abX?Qyir|-qIOSQub?$x z#%?fzA61zDgsGL^mS5Z?3roQ%ywbd4-`Q5EH&jCN9AgH-pRoqhSKVgWpxIO;h&g`q%oaJ+*JHTR!0WWf5nxe9KSzSG(NUh{;m3-mGo)=#DTN^pvXg(IOK>`Pn{lZmuiPZ ze&CDTf{Baw`Ik&TgTQTUY>=Q`Bp zWI;UiAN^qU^*ke z7ED&v=1kxK=8lOEpix5`y!TYa=5bqJb-JJAxW+atdkeWfT~1m|z5NM=8&%BY0FlWS zi2X`VJj`OZRLJ1h7Euu&0ufPs9LX_hKg^0PuY*!c^tIi0Y4^vs6ovbI)IYMl<|xzn zZ^y_hT)8sS0(}Nw4yYCIzFWwfOGuciE(Gd(F9AFErmQoyvTzr&OGnZ{UcvvfDlHn< ze~~$mH4SM60%@pPm7f!1B<6iS3`bpEg9wyS(#~>1KPnu}OYhqSdur%>sWQysw?-FF zrXpwdN+4chbD|{%Z+zh_SYFi;PkRFjPca=;%w6jKbpJ0dr}Nvs-@GA|DWVAS_Y~lD+%?J3Lu^$;+Y8267}0Ko>$*G2v7Y@Ooz&JAIxdU+tRsA z$3qJj-$n#;n8iDS;dg{Q91@xN%^?)InLSI=$Z0s1gyNwdUY2FyjfVmq`2HM(b|Rr+ z{;=S@d34!|wrMwAOtOotn+z;0WG??k9=(%=ut)=g@vN_@ybKar0yX%JJ}vDTIZjLA za0e!!2O;)xZDy<~`4@hY)~B(D#>Fap+5ZUcCphHncIAK zX4O#OWRB5LB!VZbv5_b;nINQFd{SbBtXy1uKB@ukPQYQyGV@vEJfPn@4tQxaOf!q; zI#zESL$9gvaOFF!u6gS&^frKkxIarOcAdfZ{34U|Vu)C#ko$49))XO)-{x=mbm=&q ztu0^#0HF5w+#eIN*=em6k`*#%`#LPFw_y3d-BF&@XLEggAU##V?~m(a}JOQrX1XU>g*tvXsj zW)Sn3U;>RBhk3#qQpmb_sC*L0-aHx0i8+8HS)&G=GfI?57Bz@1Cyv-;Hu8u$ zW)ptQyq)wP4kOX8T#Iw6l@f)f%v{(wJJ?l(4x9+iZ`}2A0{8 zleEUDnrd%{Wxuwtp6S9hspQChePyt%u77f0@*l9M5cVfFFG4B${!n#AO!h@MumYr< zmyBH+&30e|QYa`58cG(t`Hj-XDK4`LnBDe_X9RnM>jam}d~l@cv9Ygd;R zZ?mX2c#|&8nFXkGHB4P(u@Y^Kae|}9pNJ2GS~QU_c*9id$Rc*+i8tQVYff+gLmU%Q zsW1Q;5McpSk8U;uFdTwyukjTM0)Fh=ysjc%Q^D9K#Ud0I%|0EI4Vr&p+%E&Q1Q-ic z*Sx$r*27ayO8{S~uIT%m?jj0&A+p$WZ@eVNezAuEY#_(UeCyR03^A;yV_2iin zD^VZ3^Gl7_bx5SFYk<0=2bDTnLtX%7S&Ve?MXS~xEvpt5#pFrf+A>5C8qELS^hpCw z0&h0a=nHAdGPnC#=2dv&fJ5v|CP@b@UlMLqMUsu_hDo(MR(Bz8{2gnzYu-g)Jvx-l zi4n7rb3Z6<@|pn@YfsSie}+Y&Z6 zprWbLw!(E87oI-D{HKBrr^$ozBN9?ywkIg-ozkoXJ+Tf+qAHTgSlkE$IqRfQZotLx zdod4%%`h7tzJJ=;`0ry=D$lL2#$d;>V z0bu^P7Ks&aj&QDEABlek+PUWulF;}e-HGBma~EQpP}XL`$fC5nDYa<~aI1-()0V!7 z{JC?TD;}4426e0!`ZAv7;mg;9zUbCqbvGu5S&bk&(UC9{#}b0ni{J2g^ef4P0%pO2}wiD8-a3Ss{e8 z|CD4TOm`>!;Z7yD#>Q?JhQxw33c-})kfLa=QllHLWXEw$>vXcnBNJRV1y0=*3I9rh z^d16^C$sXSY3mfq%kCONX8)j?{CQRNut^&H16|3LG#Z_(kwK0aLy59mEUM2RONdnyt%SLR9mjG(=7fmlU2DVg_qhvdl}c8-|r#D zMb?vIGGcOIH!Jm=H32%DoiZn6Y(3l6B%ewVtU^!ORDNtOnsmG%I+cVNyav+eJXdya zE$O<4HB}J{xbv$-E-dmt78e?mjp+TR9@ip)kw64xm6f)Aj8;;)i z+94&6_bS0Hh@%YiR&e}~p)5T+f&?52(CpICpmHU&q_dm3I z23|wp0c5rqwUGM*Ih|yW>9G1p2d0H6c<(#+fb}68LhQI8XfqNc`suFagcME{;Q|L} zRhDnWQ;YfkGdHR$U($3*VEP80E{a%{RkrCpE&AxG|9{IipB1?wdfY}Ya*<+@5~QYY zo0*Y|%(v5}AAb9q+0GapS627`%GOViUd1E6EQW7>xh!A2v!6){Ic)lRIZ91tGkVqZ%emrq>lyzaRnF1xN!T@sM9 z@Uieo6f_asT2gPj*2wAI`Ix@JGySbUZX!!i$ODAE6@Y|8V1Xi$AL(#cSPnw0+|T7p z01q2xmZc>*i%BA6l}J3Dz%{YN&JiBLMM70iUlQJw>)qfsjUk1m z<^ifU0_+h-xoL--aL=RD;R_%^%f=W9&oh;Awe4{5|n;gx}+W^wC; zEs2!c_$6_yHAXZLgM%_~D0t6hCeUDju{VYi#EO_?@Ng{Q%C^(%xu|*ozKut`2YFEu zd;Wd3})AZ&iJWermElfaBt$j?+` zvZ0CZBb{!2>F&xL560uksuujrY_Pv)_qU4hbn4-;;5;aVz}tpN$)H8zJ-#+Nd~wLJ zFcXRC{gVMPT+3%6Ue~PDM+&LzvXuep6rD_xY#;N>G~$ee3(=%-_;7#tHA~U9)pwgs z8;^&Iy_&$+$ebu*3G&-fc_2E*^^j&%M|9S!S&rV%NJu`@JmM(eiuP+trKr(uSgjyV z{^cr^3Cx&|dIv<-MC5vsBX+dY*fid}Z&p#|GSGTstcV1}I0=n)6eL?lKk^$28is)` zdM73C)2umDNJC9wV{P^`kQb|$_$ihq+Vs7s%G>aJTv(XLZe_5c`v2S=%);SQp?sk% zR!U5=?zSo{f7b8SYuOA|+_rwdRztgap!ncdir_0xBhDO;e&IKRM_{*Oi;MF+CnqP@Er+T2aG>Ow9hWRha@f6|?BXo%QCjj9 zu4SnLd-z_?jd#%Or!tOmE#+&S?=9SnilF$R6xA&(P1+-^x;y$mDq)s5?jLp#sE|6e z=KXX-!nU@ISEgfZ^a}f?bE!A)hY=%7`9f#bgNSQs8(gHD@a$lT?WpUmiHI+>rYoKD zze!-F@hOSTXekdYMV8)PXionm>!nctGJ$c^?}?Q z#8m-hfO;6skRZ-I_KtFksrutnihtzW_4mv8BI;b9*)ejTP8FW0&WF;HE^IqR&17(#I<76~WFH>#5XZ6fh&LxqwgA-^kbBZG~;b+tfOGNWYsyh^XL} z=1XHAKozUCjB$Jyj|WF0^@P{#lgBtz$xExAcNNeZBBSgYS9ebDlz>9N1_KEB+1Iqd zV+psn<;mkh?(PrZiVc>)YSFaDw*hEkyhwTjEE+K?$3x>MI&-$7W9|s%^vCU`NG%Go zlzh@AnN^eXps=m*#n&BXa6_zQgeY||Wn;A7A&ju->=8-cnC|6l?PDYeGh;Ct;2*UM z@_Zmy&>zhd26jPmpevbP#OjH+q03-oj(k?Xw;+N(4nQ=FoAPizgFcPWqftTB4s`_J zf*EDzsRQut1|zEQ%CsafRkKYDvzcz{y(e>dL}cR&020v6e|KZPMsjLZqPpH6@&=+G zjltimj*QV>lBC(3zY3Z|;ob6sn~-o3m^~Dk;EPCd?n!0knRE;gPuUYf0vpV9Yg;2< zRfZF7GGr({o)2Lz)AijNo5@n=4T~vlx)E!-GcWU}2)-H|6r97S-ciJ>qIz`Mwc^YL*TtR-*8Ym4< zOV0Hxi5CqTMlX;GkCzM~(ixD#@E)W263Z8A!|Vz7bC(da7N493BKeiZu7iZ*D%Lt* zbiT~AkdAHvR%eeT)g>&HIy41`P~j|T2vPuSx}4yO3ipw!r)4C*lP*WW5hPN+{t%*s z7hoO2?LbWI3;Osk}UoN;LISbZXazAa-nh}EZ&q^ z);1V+9pP?rrTl&bNg^xJ%?8UshiJ>>7HBdk?zpAbs7LH{ZN$aEf<6@{5n(+Z)A-G1 zsJ_dsUpistWFYYXc3KZ{kWtXTSG@B6@Nduizjsh+oRJQ+$$ICWybEq+Zs1zlZ{fTs zApJ8Ju(Ov9U?DLVVx0)^vr<&6v>bG3NAviH0jMtrAKC7XL#>+9ZMCmk8<-K4(ANH9 zExOCrJofvx3N|dIxm)?9R+NPkt-tu;u}v9hawEo=gL0nq)Fz0q#`XTC zzfhq2G`~uZuMUirQdL#8?rY4g9Tp@6xfJJ}J_l>*o9Pd$$JQgc2Mc2+gjgCl= zKx6LqOAqIxqs3?Q_@zO^d&$TOX>epMSpcIzx zXJ_$zjbyL^Cl}WfBmMDV3(NP>TW~2*r<>I=Z))U^cH{&DiUAxYGZxB2-0nBZ3umSb z4sw$AkQQSmoBgk+Tm9oiIWh2l_=dTg*7N<8pJI0m8wIm~50O1~+}o_!;$ zqs;1W{0*t%+hMFh$YiJ%VZafKu_&z!a5Qq*Doh7j+$XVObJyQ_%>%8=iQfEBV0dA$ zMlExs%=OUvwFE!SuTbV$I`-m?Vlge&0z_P5BJfKO44}0LEg* z%ay*3i=`vuERFtF@7a3HkAJ5qV1RO8uvQ`TfNIa5)`J1WMF$?Eeyg8Usq$b;WurEm zoDJTStu-61lQ5Bm6&2dH3w4G&L>21qg-N!Qe`pG%*ub94h3!Upw!XcpCS6DP*Ywy+ zq6o6((3X|hZa_@xbU9`PhP5f5MOK7s$O}Bw`Vwokzg!L7dMs8CPx4_moovcO%?GCG zd6*MI!I8WLrmvi*-Sooovso5H^&gUNZF}l3*dX>jz@V-}zquXbue~?)A-Jcmo z{g1>feAd@c`!vlE{W2aHvK9t~=0SINvqGUnec0U#$wYYMmG@{m1X>Meb9j#1R{;hA zATo96uDKo=`5P|b{pem7gfJ{Qhj~d@*3|fL{Y_allx+}KLXnY`Nj3}yti@2+-*K03 zb;)=BGcLLLC)x@1BiLI;ML9RsExmdB@3PhrWn7{vo zy@MjPxpopV@Nt{+W|kjuyc71}qrxK7K~C+%YI8dD#aTry*r+jFyt5Ok{01Z3K`Bsf zuW{Xa6mv*D$076YGOam+NV=8e(Zj9=%So3#tCO4D^z;DOO#?4K1j!iwE6Vs18Or-_ z88krv-NxL_7#CwZ`NL6W$EjwThRD|GrCb{@Q8y~$6QxfaD_dZ@w;skd5>vJ_dYc*? zHA8e$&Cmg}lIe_B-O$MfcbVqM8-Bir;37Zt(h6EhBVS%r zj?xzuhQ7mJQ9a5XAT-ZJtsw|t=?0ca$i+hg~JpOwIk^Dl>>r1mS7Wl z4@F}rP9_$p0{J6Ww#S$>X~l0M%hEo2r(Cfae7)92B({_?vPbVX0=B8D3cXB6#ppfz z-~my##P&(*%GI`dcPmqE9+d$+v8cg+--Jm2D$8nH%9Fdkc?HSh_cx@2n?VOK9jY93TBe!J~mcKWb z_*=u8iw*@4LDfn#gcB_>uUP{7wxnCaUoY&T7K%7)PEJO)nof>xihOM*!{H}HnPLbm zM{QZ|oh9qoZfcCtu_Y7FU4~pIp8)Yh+*gMuWP8(TD8qf7p0ACK3@sPDn%dbQ&35d+Dq(O>j>{0ur zN6$*nMje60le-B=(#j^j$3J`^T`y1imMi69nv3Iav&k~3w%mxE1dVLQX2?oRF-lTnXH#w)uHPi;nWQ>SC($LBZA)X+F^%GpMB;T;E zvpYM_baP!T#ftav{`5)PdgJT5Et`E2xh#IL(0w#a#>bWX6P~7hhby zzHFUvGj%giTf$06veCLVBz3{c5bgHof9gdZpSquS4tiim$nEiWXxHl8uSyFz5bB#C zjseM64ZPnFeD|=cO8eTma&<&YGu+fgV{df*t!xzeoId@=9wfAjRpWR?=KHa=LBMJU^F zqRW4P2jZG$Yih|Gipl2HGa}E1^5OF0Nl^2N-NG7=A*Vg>YY+JiVMHVu5acTbl%q$) zujBgI=giA0kY0P!d)4|45Z{Uy%2`PX)v{<;juWM(z2{o~NY5B^#fQ8H9~Hp(22|6& z7$X;Q2u1MUff32ytRY7pW>Al;+*BGlRO3M1q~W>u&Xs16IB|WA7WChlY1?@Q#XHqi zjw==JXFc*pVIul>N?We^o|Gu1x*xASp5p7D_%d>Hi^+RdsZz%51`pd~b^w8X?@3S?3M&fLO-OFa<%_n>OTt#*WGkJw*fp3$Ix5@y~Eo- zSf0-y{G6V$vtC`^nm&0#+{W7*Om%n*Dm&l05p)l=)t8p8x|K;gdGPSFut5a}kZW06 zz&cCxvKbHga^4~R?^#!O?f=t4(eyL6<%++l(nCFMxtM;xW_|Se^rPhwA#Is4{>A#@ z__!u3OShv~`9`nPRs`lEtt-9~l~&GV34JJxRHs_>NHTy`1p?rJq&Ruj-_hjiqb%AF z1wJJ#t(dsX53VDTiG$i<)Oa_620v(K;0MFu)INk2+Z_pQTy`W2Tr`uHFBU-o{GIkU z&JccqUft4bP3FsHk||q2&InTyOGRNF!ATlvSAhL-D^f2C<71*AN%1SuEo1;pj1qM= zT;LJoe&|-};FX9|?^vHo!PMzDhWuTxl?`q4$Q=#pZ#HLNstVl1wkEO~6>tVyMlGrR zY-1jP6^tP_SUeX@@uZl4Qp-B7qM4_x$*_QhwoDnP*{%e=6*K;J5iae>_godXI$_(& z6lWvkoQhOdXecUE00pt)HRX1T60#vGB*U#7Kli5f?XBpwLGDN5fXqPJVc(`ETyjN= zMXco3FacK_w$NAYR!CA6erGk20Ghw0+b$Uc-Hf-y9e?z=E4o@d8df1{4^8J=U7y20 zQR(g4gY(J^cI2-!#_t;PIl1+~&Wmjrx>AKSDW;CgiK^OOY`_>cJm5dO`139N^C1-< z16dUmnS6wg{u|R&*U(T70$GUE@S@m`-or9sUq?8u#$ahAV*TNdy3RLVA7%BPSOvgO z3`M?>3y<3p!>*@u#~jbS5-ioS$z)*wDB!7yV*Xqg`nP?5(JyK?(iG_HFk&2@nvD7E zCgDPie_l!CdolfXtp7OGb7t_5K4ZYp3h8WUU8b@5-un zf`3)XQShz)-(%o%_usafkDZMun<_edu-%+aYA~az%DzJaeA^nmF zo{`XsK+qR@Dbqz!A=w(_{)9ToiDR^3og_#Mkr1-DCtCforD;H(`A(-dY@Z(N)^8~* zp7K+v(W#WEaow-6D@sLnyn~wlw=dqWOp@;j;5D?Av%Dgx+~x#33C096spT`)3wZELj=-*00HA_s_@xN^kQvR*4ja4iI6Jr$C3GU)srdxHioELQF91nyrVn4 zQe3WaCVj>WwcHL*BII#n_BEYk*LuDdWU6(Qjn0o!T_Ggz&jDXx0mGpp#Qx1af)@%Z zfZ<`AFNilJLIVnvSlSGaxz%@9^^@uZVZg{*lIr5(A|kO6Y$#oLt*H3AX6!H?F3z_c zpO3OU-&bY;$q|v-bOrUa&%csB;g5FobyVO2OlOgbEKHD+MaN{&R2E@u!%rsXxL9^w zWgw{AEbOEnHX?U3;}o$Hx5OFl0gqs?X(aJGH*Imd#0YLB5%Xe4Nq2FZLz|#YtbDXf zC)O|?lg>C_I61R1s-z=Wf2aQ^1(-4^&7XapDKYHU1hz}WHjdOGDZ<31exPEi)Tvpb zEv3st`#Aqme@QGfua%u0e6Z0)Yt2HL;=vrLugg=L?CeanxV%FHbq6B(U_BN#e+hU^ z#3Rj#DOABtz-d!$_IV_SM$j_tPT8dQEZEPJkh|oE2u*|3KtAz;`@!~Pc`k9AqUckx zebu1lhpbq+qRb0Kh0!)Ac9^{wB1Wh1sXcg z*%q?Pss0q0TQj3t9rJ)!SVwa|H#)9~nBZ z00yt|1hh`bYIE4?aQz-w@x*uqx@dWYg^O=UH6`b1DUUb0Iy$;-O$7z4ovn^%rwQaL z!Df%GV|qgC*s;_KR!!wxlhzfhsZ0sa_KFR@J^_41Ia4=(LC+Hb^}9nfhM1CNOiqWBWX;%`DOI;lnn*7=fhZm zsZz(x{#MMt)j{6AHt<+(sikT|uqEupX)^KRB=B`zH6TE3xPqrzOLHjYATPo3gt7M^ z?5O4{sJ)5F>!xSf&ND0XkJL?;tK?R;qkSAX-APJOGAkbIaoMyE{?2n1UDd!XZb!U^ zta?q2!Y=fr!pvTPD_Rwq+JI7s<_$x^9VWgnjM+qy5}4Kj`e-#Lg-~!jeAw>Kzc4(I z6;4+UXajg+@KiQ+%bC_h%v)dpkXe@`%Gi)UkZ3$b{~>N(o@Z>=*8>+%tX29H?~&#C ziTYiCzpXYtUZ#Nxc#a7fbmN=&ICLSCJ*cuzM4S*2z_4XgA%Sj(R$xRla3C$=%xR`8 zy5qCUNRGB?dyW0%7T(S)Uz*j?8d}IAQz7q{CU!9-Ui@Ve?w%RMrA8;AT{w5JyDFRC z5>7WUSi}U?OofTW$~-xwsFAM)2SfRppeV8`N2=zI!)qcTbkWS3a6a*?RfG4s_1fD2 z6%v_!jy#(qLX)n!;X6~y@*K@nX!7X#h;?-qZYoO)0rGVqj|aM%b60y&KkE;m#Ph$R z&sM6B4QRhQuFxNB6L|Tpif_YMiRxfKYZS{2bqb~hF<~$v0E;CJ3NHjpCAV96er+Q} z?0<5Tn7>k&WkE}n`n9WzGEZ$|*_LxLzrSr~og>F4eI2y3A+m2AXZfWK1p#8Ns`;KD zy)!%~RT1yn0H78YmdZWk6SQ860!)hfMP{Zc&wJMezQ;;i&aSp@7yE*}Kd5y$rD`RX zP@vJUueYPx>Lx1q!NM`-nqY1DuN0||CdEGyM--5#criEA+n{&Nn*e1KYLouR0Py-4FI&E0GzF>9 zJuiMAy_9ad+V%tG_|7Wc+$1(XRvg`asVgsjzv+B@y?LnZ0XH=K+yv!cMk)dMR%t?e zhGLN(EqLSohW{4}@HA8Ty5@1@*L$?C!oa--1;=g901FOSRc?X+(167;!3F1A`2MhX zB}o=cG#B19gD=uQDKziq(m5V5_G{yYCR2V5?oB8RSJNaooKv_Y$x)y`Y9JD?U@1_9 zv8%ucbJtd$c9b4ZrZ43J!td8!VY=KhhiUqGR<8(JVw4g4#MT$!d2TP3RH*u zbYm?f9xUvqthmLcB+)O#5Vji`Z4wpz)>(A(t|ulgF3je~I#`St#?w~aMK!Qo(mAMELgHp(M%zK$X-(E;g*No|g;Ow#S=x&!w@1 z-;|%Aek2Ef`u^xisUhY%GTn!kl)W9lo9>NT#@{*-VNp8)76VNHX#)G>=-(?fhIuI! zl-*S{2CpZ57C8Yr)E7!Xf{``Lskb^uz40$F;gck=dkVG^bxefqytV?ScgF#P|0+~- z)Bb>sXi)I$+2dlW5P6>4{%j~T&d>b=#4fpTT9q{tASmE>SexkVht!Xg}fQ)5`Cpzz|dtV+!<_qY9TX?GDHv57Lm;WYY{}p+r zN=@W*Xgq4@y45CJ-6>+%Jo+=lF-XA&0sz8>PJzUN`Mn28lc)&pLM|LlWQREm!k$6! zZ|Z0vSU~e~TEJPP(3f`*>su|~O%}(k5m2rN*Yz*9-Lh6MA#YIhC48@P@UM^KTHo5Q zfw*cp!{D*fOjJZ21fZ;ilFNcu#P$3dS%yp~(S9&y$K^?Dy$_AJ=xwMhH2h|d9(Sag z;KAR#h|AUA((!EcMo8p5r~zhvYQhAyi88V z5(4rB4P+vA$`mV`lm9WCZ+utRk^Mp<vseuRrL8FFAlU(#>yHzqJc029_6IhvC|kR#s_`HsiuvS-Y}~{chmTtG}|E?bX%N`no5DXJf;b%JHdincL|}g@=dldE?;wHJVt;Y09<3 z?)-^_v9iNraW6m#l2Yw1uITW-PqpVMH>r!(8_3t=KiE<}7R1_Ot~r-T;Cyd+C)fN; zJ$RFP$G)+~l8VI*xY#sI^z|8DJo`TNDoB_oa54&7wAIyA)~563{9N2L{P$K_Rp$$` zbLPIcbx_j4X=!O?>hp+@Tne$z{km?I=ChQipn>DHP7{>ej-dWR1k5mad4O^Vh5?V9 zE@pj)jr0}q)#{l7C&w-7OM1uNI0in))eDw0ueY~l(_~7Iv*dAvf#(d^9{RUSa&;XJ z9-!Y6wc~@6?TpoS8c5vk8wb!Mg`eAd`5ZK)C|1V=EfI-k*tzER()%6?2vVQ+QYm|e zy+1)!JPVtN(z9Me2O^%ehi|r#aqw?}9nNaXJum=?M}w@P^T(x`qm=&0iqPa{3U2Va zAnmn>CHh{!sjv4Z9HzIAaNAVB`s1FXs-4ermopqD-kZ#e-p7`&4|w1r&lQ;-G{Vo} zNni17eq%g9wO*5%`q6xxM&GF550|samf%%9qGT1ruKmWPsb@Z=ibwrGB zVrC62MrnuyHrp3OCgJNeqgslp9|Fs+GM2 zDL<>)rgns50R*G{H2NPJW%XVSd@(i-w|9v7KsUUq+EdtX{q*94x~Hf3igd4T?X=-8 z(fuNl0jcbsaPq)_(%1)C(&yhV zrWyN;Cgje)l^8xjBj?K}efNVfij>F!CDw}~rD{a48@@kmTSKGr3dr-lN_EUz>r~bl zS*8UZ?&|zLI%~`AEwo5`8W^=|sIfakQ^#P!&k&j0tP`{~bv@4`n*KffDKZ_6F(w`V z<&H1Ne$jnmsH4PHAB}Sb7XvqTR4T{VeJS_ly*S6t+_yj3+cYX#070g5fc@28>+|V6 z8elS&X|9tc*XK6OlgIZS%`RA?L`wGQf7HyF>nsvKQ*nr=golC z@xJ|DRaNEKxhJ)kF`Yuden_#Ywf0quVPdhDSj=r>k?z4t=}dGetddWo%b05-^C1DR zmFSbo4c5%n7?#+Qa+UZasSDC&-^llI$prd`{jddVmt@JUuFJ zsko-naQT%B13_9;L`}Qzx1%w^IB80K^yd)E6-HsBNxCDywb$`|WRAg`=y@SZPvo9G z{nSG0CrdTigwCi7@?6Ye^5JGV2~}1L+2Ms>it)MEr+#m*&7M17&!C=&s7TPJa`^qk zNh_J0;a6Ip5by1U+Y!^F5V6H)Xv~so+oej`QfXVa^^MS~pkT@4U46CU95piRR`tGd zpO!HS;o@zjhvH(7QQHT~>0iUG>#U2K?Ny~b1^S_a692bi6!hmMa8uZO+d zoKHqga+8!l&^JJ4|L$_7ver))A-)SW%!==rIcsI24CGF+8LoB(eO@v6vTR%pEFNQ1 zc$k~$3w?Z3eNshS$|@7pUex_Qz$Een+TAq7#=mDt8gBBF*=B6uaajs%lP3VMcmFtW zCr#carx)EH#0m0TL@TDm#eI?yvwJ*zv&{K-N*-9;mH`g0P12Npe(qk^{cw);^@iKV%qQ1mCy91i6rn|2B{tYQ6YhD_v_gqhfMU8@vSkHb&%D{jGaQ%U&Wglxo*{;t5 z&r|>QvLnY;M-|ue&;h?Ib$vfk0yqNZ|6x#F1)PgJ^zFVRDa4%Dztq4bJ>mR} z`xexQog8_WYsXI?sd?8dn^q49eU5Kg#+b*t_9(NC^ht?8zXvZ#z9WmGF&!J@E6q(( zH~BM!bwD7d9azf~${?nTKgpS7onl~i>Tt^8?b!>3!J5mnd=zx8jG6{#*mNXhOr@C- z)#qpZdN_VS^`mmtu@6qQlqG}r=`abK(SRXdgTY_XL^juDG}~={zilTm=QlOR0Bs(> z_eV1AQ4)2UVlKaN-)CiI6)2OxeXCkJOF5e3cb64~t!sPJ`@DVgxeK$-$v?{qg=&tV zBGzu|t@omx8d(-fp|l(+oQRftvHR!F+vy0^b)quc8a?Iihc_Oz(KlEbl1TAg0XX`9 zmqxo;Yg=K(4L)z30YBJBR?|lJ&E=ezE4j|qHsS+{pvo!(PyRX1WQQ{n!BD<0T(se= zr{JGoESv>VQs%1YUf^?Gt^eqxLsz$7(>GjLRmzl|+dx^Cq;+3yMn3h^k ztca^~?)|_vV?qYMBN%qd znZSv};vw2;4~koGk*0dubu}u}7lmq!^<$uaRup|!6cx$$|90;y3jUXoW+`DLfLlEs zzfN{$FK(+dUoa~bnEnh>!1kaOiJ9>~4xY*N7EHNMne2NwCC-&jo?+rKM$VUp79V=v z*7n(iBTmZ>9@Oaqxz;%;;@7>2HqBb^n?mM*fTw52KYCu|!DXWO#B}#jh9Bq7qr^=X zkMFh`J$+8fR#4T+qCa&VvflrSp{UjV%lF%CFU!-eKQDULtowML{Cat>X`8C4uB2j} zR&;n@OJI-F`kcF)P$w4TxIyvU_GboY)uT+k|4v}H)TlAIT_q;^H*POfRb9<{@rNB? zv{yI`Fsk{yUzpMJyy7$wP`qn~WpPINYXMv~-y3hHZ46Ev*M=?E`<+2N_e!EyHEZ2? z3cC}Q$SPu{6g;`YP9-U(6+c`=QWkWdE;XpKoUXKBf%Gf{up%7e5KG;#az>SAD-*>W zJOKdCQpz!2{Mh7dEGZJ#t5>CFp820tpOYI$3_PbiSE{v?U+1I@yFtWc%spy>%@K}*Wb++g zSCi0yvu*08KvRQ4ovaCi@4dVhH+bJ&fXzT%20b~74m!xtd;vx1sNI7JtzLUUo`)sOgYOz z!Up_GqwGsU`Qy|8^|K*_+T9&#gLtlSv!;_(Y*FkG9IXZ^jZ)pV%DT>BR0#lxZuxwF zuF}zEJcAoK0(lp+TXfA|*rgJ(70yQoOnv(*Gxva7U`BYIzHW*))~~947X60G$~RCK zZ_n+owPhWE?*UTNt31Nr;V32=;_s=l?PtG;RgNYnM^n<`HkzH;IluN1`kaH>M5A6IsiWJz~#M+se*Gn%4VrT%dH`MtIy;eRLF$C(zoIkP-geMJ%Y7%LNclyS&t zS$?dte);vE?OY7D(r_q*nje_7iIXncFgv_bR1%u%E()ibD-UiFRa61VSatTN9|&+8 zmtCj`0W@mB^RW!}IUcQorYN$(6qhq=)HZxmEAwa(NYpb&I1Kvl+C^2|c^p7#6Z!lh z6=wVB8{a)~peLRp3Ym|BB@R+yHihKNv+jUc4F)d{9bqetYAR()I_uEUbn*l(v?YlP z$}1zRAJtka-)v%mdTb%jq@rFId)HU;>nkFGQgKx3UNPbM(D9-eV`a%hq`|HlR+JH+ zS|Bq9qc^ZCa{=k>;pvy8DPv!at(^lrhdr{^%IY7-q~X%<8!_L->U*+u<_b<#Rp$Ew zqxv@2qxapSbj6-%X}_^?-z*3rC=sI=4o1`HOIGcp(!qLg1$;1@D8#c_tZjyiA*kfDb+olxjK$hN zaQQ3{#o-ClU@h!l+nrVH8m$+oY_F;n^Zw2Lj?)6o+Fxl0v3wSntE|aymIoOorySzl zRry%gD~;h87k-#BmNe+qKyOx{NI^TPue(ckA|01Z*bt4aEz*%bZxxB`2PB{h6O^&N zC0{m>fwzEqPH;;+fuDgiMQY~|JA0-$OBBbw^SH{Hs$FQ8>&U&7E7>A})(Ou7iCffo zUq(<*^PLc+y}^u>Y{g{pNBrx0nM`wW1x?74W%5irjuS7Rr60$+UE_MuKK6V2!$+!A zGr8>HDjM3wk~N|Q#(u7Qg9nN{Vcc<9#jJlur{KWPo72t4VdFe5Q1`LSq3Uq#;|AW z3q=83e<7_VaKPd~P-cSu#mSFvsYj=O6Oor1bPKQRT6r>lbDRG9UKuE)D4}uj;9=_z zaRi8E#P0{ad5UgghgQA`e)If4G+kv>8}HK%ZUut7yA&yf;_hz2tw?cqcL?sT#oda# zd$D50-KDt0o8SMOHzyyG&B=b)oqe7=Gxy$UFUKK?aWs*DA|}*G(``Y1J01M4ly&zpbVk>YR5!L@c7E}hEYhb{(b<* z&Mi>2wYlNi*^v7Ka@5ahH-=oArrk!Srbrc;SZX)b%0&1q4-qh)VGaeTIC9edB2=_? zlkGX~@ztlmH!eJ}pW&sz69t;~nmef16o~hXLD#fMSV*&xh<81|KZZ`3^2G$u@lKW( zZrTU38VpEP98n)9`bHr4Jfqt|F=21d$+)^dlq+Cnioo<2^=AY1U<{%8#5mi&TYT;-scNAgIx%zjeytG!BItJ$HTU#(6xE$r^S8v z1!GPs_tA?1ga>@>Xrfxa*bG0lG>R~U5GjOGk2lyIfPGK#Q9d=ev8NAu3}X~$joM^= z9qKqWu%FJTt@XQr&tb;&V-*dQep@>jk2jVq(IsKX%5;8PtH-GzXpz;KvN`r&muM0Or*6C z{u@x)1W;B`(=O&M4)Xm7m#7N$@MU@`F_y!uy(f^&kA^T zxR0xDO0lkt6+AF;c_Pd67!Q&V4~?K^ZHa6K7@1FUf2!(w$VDjocWEdTPFS7{9gX{R zx>O69(941LYq7h{VRmI)wm2Sf#a1XsS6kbx8UjDt@0#1pbJ?SKBjK1E;}HPG5b4x( z&duM@DBPacYoW1m%(&nDiHvR#H0hlij!491!w*THa?>A1CkdqfO-0Nf&Sy{O_zM!$ zB+1}1Jw|gbI>iNw?lZ*sR`tim{87cFyXLUoMdr*k0gS>XsbbI0Dlr6^#72}MhRO1f zGPSkJI_Xh=Pxg{$s>_dg*{9nJ)DCxygZGW=hd8f9CLI@Z=Pzev(n#nF=^1N!TJ;L| z7&8Kn;3V99hwU$uuhFNkAA$smn7?se6yST{Cg(sIsM7DRL7#a24=q+iVhWUwdn<4ZB5pVh)6(LIxVX2C9`N$C0eJG@_Gj8Z7SgFxry1;PzbT zu9)AJVIU~xG;Hogm`KK)HB~VxQTkh{f)$4i&?}4#Z}Lv$_i#0FUMnDKnfqY~QP1FU ztNcFi15&5@yxLlJdh*Bh>iOi5)MxilU*+ta^|#$iCqGzD$PKkI7$PmSsq^=3DSzr^ zP5Bs!kudwN3c#wIEATrcNi|gMkT%<$!9jE;Fu3`H^68;Svpetk74C5HqRjUyG|fW$ zWiG0A{$ly+2}KYAko&J8K}&oCdoc0kKOm}g0~wX{3TZJ=B+@H zSqi)=izD&8k}yo?e){jnwJRa_7*eH!I*7tnc4`_9`GFD=kn&4P8a5*mC zeaKQ3G`s$o^7eRpb_KUKJDo4jnwSdVn^c9?`k(f&bZ-SwnNOJ(N#_KCLxRb&zS*Bg z;((Ym1z)#S)-3UrI8(-&`IX40c&nTEwO&ghpTd2yOp~tN9ZW|^_(7A@F?|o!v)Y@@}iu>RPONIN;#WTELm*qcC z&Nrn|e)Ea^X&W*J*+n@obL>Tk0Bsu+M{F*>dv;+?my(;AI2=zrvK(_+|^&vwTxBhAW>>qxA?}}pEpgp5J1Fk44deU z%~L@A+#l7TahN)JnfJ}RCB28uUqZeIrM)Xeeve7IeqJyGgxHxR(>J5=Q9nVvh(2jl z!#nvY)@hQJjsS6HR;enf zH@DvvW-k3JoGAb{KOY{z)YI%8cy{F_j7O;TDvV6KC5vcDkD~i^$GKq}@ULm2hw^V< z)bv|S>ghHz*_MlWgCc>J{6qAzHae_@aGG^SKKvpD`>B=0nJ|M=KO!LCV5k)dBP3n; z3Q3*J93d8rNb5A(z+wx`!NY_>vCGFH$Z>Kge+PpN(&H@GxjY-l3XtIOVKbMpI5;Kg z_{;Y`C$DC*>5gkvs;#IkYUL{a$GSm~%1<6(0oGIOGK2=Gd)qxCmF9YFKOKI&xAcI( zn;+Zg`l0n+Z@le`&KGBZe}h#1k5leFFInCf@6!`KxEAN(Z!p_`V1?7@mxT9;7qJVP zw#W&2r|;NeW}U-M$59xlANbwnXknZtrC-N_UNf zuATk(KY0z1z)9f~-A8wc-Nf~}f8NSNV6l7CjS7aBrx} z-m2)d{+~{iKf;U%&$rb~W+~-krQ+}!b<;HFSE>HolPClyFA;1lNQg8Br9rrhI=qO8 zKRhU0{^57$qgbDBOA)*Mqd@rmvMB|%)ru9H@l;P$1dy^)5MK76~PmqzS= z*r%}jNT&Tde7-=5`Wu9ql4749TfUXH1UfG=Xe%wJ} znHkP@pW@kkyl)YF8&d+wrvoi~r{5m*{r+l}y{6^)`}O;&0k5tnxPK zYzfk2xh>gO+p{gEmZ(C?Pj--MseR^jHmLJM#Mt7NW%*s1bpd3%1|g?n)tW{_R!E|az09oG=d$S3@dsCN{{lV%mt*Aa`di&;uLH~W#7GiniJXhf&dikHloBp1 zHMi}PbECBp3?DDrmsaAO?jiNr#_~fK%anFXUUEvvWDfUrYkeI*ij_}DQ~-bqoDP#j zhM-Z^UFXbb*lYwfhNk~RPikiiHlWA%_qXbQDv5L)!F5ybV>do5@gdZMf8(mI(^wf->wj?@z7HQ7|_dP z^x2$2G#!HgMrp0FWy9~#`7p=Oz~DXZ@ME~r4j#yCm`Kv!fQS57ZY$YNR+x7z7f{7s zFU|(8y|L9Z6ypxCldTn)Mwx?R$`y_Wizn^VPaH$VmeT~_jk$@(G0^%}O*S|hp40cp z|3q^Uje&Dy#{+yKna>OzA0FGwM9;N>0B|faS)T zpqHUJTDa?F041)6`9+%2&;ssFe2`q4>AL|foEJ6*ZrovgGhzA_m;2yJB65cl{<|{@ zOKosTovfRQs?&nxeCdA2!tRE-#*j+dhWg)sZi2gv%Xe{ufrYU{g|VMKZ_Jur1)3^( zn+oeO0M9tZ@aUm&d3sKJSK3>#wjN}e8n?JhjIkt5t`J>P4#Q6fonZAolOh;lzd}|Y zi{7B|7N~tyAaEMxqes*NT>0ZF6WD?UJ7CJW8$wnD*T&uDpHUgHObNt3V=6y1j*AoRbz zyN1ah!khK;y~yjRd98^RT7R2Jdw=yfxRR^s*sj}E8sp!1qevUn7!9R|coT-jklB24 zjL~vRg{8S>g5Omo5zI&o^T4SlzTEoy72F(yQm+DpW!O}rXPwbj{>j0Gyxx>v9nfD! zDBEUxDIK=!{XNhqQ1wIFCiJ)Z_xU)PeuXe%`cG`qPgGndVcq!7y0Q5ac=U z^RQ0Kb|ns)bG2hAahx~@Jh}Bjfh6FM36&&`x2(GiQv=;L$s!s_vvP34;G_!e* zJihXo+&-J-(CZ*|=sa`)atZ}aAs+;K*osCM@DBlHOBTl`e~S~f$?-_B@*5Wv%{z<_ z$*=^{fxX-5e)v7|BfF-=n@J2tej26zy?1jQ(vrqP#A|Ay^51Lxrc<3U^e2Ij#Tm^X z%i}qX17Rs+-K6KgPQ#$Sxw}3h>s9|I!j;-1j#)v+-{mE3hHn}>*-+&ec(L8z{BNM# zS({J0m!27J^1k@K{7q6+(3z)$mNGzrQJm8M-A=V=yR4<#@hd4bIpvOwgE7uMaI!&l z^!3~2Bwc0kokSMnD1UTKFX*!`o~yp5%WQ+dA&#BDfNo|+TjV`GGM`3C^i*R@vBj@R zaTU+;iu^w(70{I@#R)wePKIE}2K)BHzT>{dE- z76S4ZH$~0!hH~9G3Pf280?1D-iX4!rnqpNLVL^$tfzI4JNi^a{c4mX`cB-d?r8LT+ zu()OYP&>#sO*+=aGYf82s7mC~vuJO0`Ci`L#YZ(t5)E zyAGo}srYU5EK(Au#*-r%rV%pnaKTddA%Qh#R;-jRE_s`=_M!by3l+CS^F<>Exdhm- z8VaD~8Hk2x|1(mjuWLMKQcZdabUN7ss8S^@$ZA)d7{2TCSZ^K6#w9$3`u2b+@JW$F z;)k;i0zg<;gHrVAv2dA%T-OLi2+x`MYp=h}4-Ghg!Bf1z0Ep^kqDA6(XCRgI=3F|!v(BpjP zPnKmEL`0LdrVtF|&HEOS1RFkno5RF|9*@y|rbmO8ks;l&B;zK-C4*MNT}=#4NoZ>t zg;_YZup2qHO5>un7Fl;NwJW2D!$ZtpwWw1K!^mybAe#Ec5sn|H0{R$^GieS+hA0%g zcU2N|#XlrO+qNlfWCq*Yun4UcI<6Mk-1&78vogrRxwr0p92K_pGeh;vrX!8-W=sFS zjXR_-_ytq!JB|k&`#k*9F&ui~(j*bLMH6(Ru2qual>M@h#gqXfTSd-ZWx4p=bMHEh zx286(i$wWM;dfAKT@`v!Vo!U0Rh)Zo*k69SofajkWV&lKc?f|em(#M>I(^u+R zVF1w+WR0ReumMhVd^@Zw_>-d?1t>Az69`yrC|?>iLv-lZ(J`8EYfiowc9N42gpB;c zMiMp(VyB=5ea2j5yRH+J7q`zyp>7yc-s0ySp}NOVf|AR&EI_?xp6K>~J%$sb#5VpG zK%KA$oub;9lV_9TI-eM-s3FV_x%ht_+!gL*dVyH%Dx2dhDZdg< z$M$s-YDTdD5{sk{Fw3Lu1{E873i>^s*+`=b2M0I#M~^Rr{0-vB1S-}i}(w(sKz?7%lgcdr$pbc(r zYQTCvV7@*{;kR9vP4Hi31b|ki>VEK>rHO;Eqa0iu1J7d>#<#wF*ZP3(EoxWzDL78+ zAV?QZ1>`quupC1&0174UC6*M?Y(gJ`RJk_vAG{7HJbmw+C%9NnaQJqhdOgDpJr9|j z+Y9!NO=5W~cIIp7A$L74#6S|0z+mLsI6d@p=d+nVCB#6EI=a$WMw0$3EBR4$2qH0s z9@uHW=z9_fJRBxx9U7S!*9*~TO*Jvf_QB|=^X*co2!?o()vuEx2gaQtx9yXOp~Ax= zy^D^}EN(licB9K$n$UYk3(3|oTCxptCYPwI0!OfgttW!Yt3-<&v}|3}o|APEpcSu( z>!v>B7|g_SgN7}3g11pZheasc<8P)Kxg@Qz3F#Z@`w*5ynh^(ML$Z(598<+44x_EtghUpF(V4-NsB#S=gC7YL+7-$C5UK6HIT+LCwC}jKAf1joX7R83X ze)4YMm<{w#VXUhlmJ9@)>s~=7>~O$co{&5^;hQUU6fxz`K^mL(c`nvdQJ=*T`tb|l z45f+VRHyZjiEiKu;4vWsvaqhuBsM=H%!F>BX(k;#wtvGP0)({&kpnRuuG6%Tn4rkl zd;^=wT{2w0ci=+-&6LNJJUv|$H7thEcF6PL;V{@Uf<}(L>E{J(TBo9Xrn`(-gmDfy z%zUY5DA-YWBT{2Z!|DSu%(cf8BHgjCyP_um3TrxCf5%LhKbRVsC^_uKjZDS$Fdzcs zZlxO2+)E6a?~0Ku3ejB~+A9NC!^lk}#YOexqbO2p48_TKu5dE(4F$wYM3S`ivSE-k z`mc0bYVI|E+Sikgz-H#)x$V&~he$GdsVeoaEuV9c0KueX9=mg9R9Hnu5<5XSg*U^ZCh$Zp-9DEo& zkG7WKKhmVOAvbrN4e~YyoyR>R`$AM|-rp*r1Lj7NjX2c#)%0If^#81O6iiB;IU9AT zeP|gbm0AbR7E0pc;h2%KvM_#u`8IP}?Yn7noLz5{i1hQt2YZp(lhpH>8u!PP0fBc& zB{EWRTa1qp$n(YZm%Ai{me$5tA=t&IDd8$M$@7-0ji8@C09_i#b5g?LuUVHp9lnL* zCwknwOr!Vio!i7Uqf>LwV8fqIuPtm%rAsME`o$a-qiIH4+7kVTnvsajX5)%|IeqdKZ(*( z^M5P!TgXANeD;~d`BLxn7Qgm6FppL+wtrrJG2WcqOxr7WS}!Zg$(aiCw<{&@o0x@7 zo}M~Y^toAX==HfEZ>X<9U86nDbcv4rSf=okuk)kx-!zcNXg z_ET}Ekxk>!IA7ST@q@R)m!8Mu|90TDd{R0?PJ}x;X-{I5yV$8qMEYHKgN8~Gctc- zB{-sD<*M`HK};%7a#z7&E&p>QgFf2ViNr1B2HyS~ap(Q>?f?xIKj0iXlOd zId%5v=BsKUeK*2?h;j(A%8iai=O%#9UYixYc}6%-SD{+vH-D_77zj60LYiF4XP-RH zWgW~FH}Af!*M$L`uDE~KY))I7oS z#IK%l5+#ajW#ou^H*$1Ee=KRUbPyPGlV$ZuIREGf?dw{{aFxYNhD>k!VtOQw!aIdK zaI8x)vcUe4@2-+FvU!?_;22%O%AwN8BaMmE5KXe%CtfGBUMT5n8qno2AA7Iu@%=Y( z``nbdc~7a=1W|7u^!OziJTRQ_xor)?ND{lP%uhEvjdc#WtDkFux_oiSGQRk%fauni z7pt$QMy!1QG|)8NJlTHC*C7>?5$XFVGo>-t__|pCA@nqrA8QOoz;2FZ5PuMd61kV& zBPt8p_=2z!QHfl|_`gsgL!b1RC1F3^Lc!}4s!ig9f$oj(9#G1>&d63Thj?R}&Mz!B zg)sL11hy2p+;1C)X2cuKM5QOvbfc2}i9xg^z$$<$?F)2}s4n2(!TS`l`gNaa7Kf{| zToqY_z{D~k4vd`Q3V#kp{GZijlTpAseq@{n^tgG_HuM-v`gD!L(9f z+MI~{LRgzT4PN?T4&wV5vQQXJ+v|yF&HXmAt-MaR(hT^Ym3$xuQ**_x{>PsYO^%;K$?UZI z?Kkxwo`cM8-s>K}8NNNbW*nsW8@`(^ehwZ%%UzDul|jzH>v3xNR_$AQA>+B1V(Wgn zbXlYp=ELW#Ig-+0__n&Jdpei(Wy117Yr?S3a~6gq#-M%tS6A~vPBe?HYi;h>d4!!2 z$lI2zIim7@CG|lWTWdj(q!2#3<32fW^ZoiRqi1e~3{qk2IeE-NZdM2#P5RG{bFePK zzS=db8;Z;klPEkUWRzZ{W3M2yNtDr~kR6E2Kb)SN2g#BVQRp5>z+j*On4T>m60=)0 zA(0)PxnKh8>u^Lswlz8qgjyk8q9(HlFRDlT=g9P$JM}ZjtbX1gzjpM1O-3)wTwj;;Fc@q$;TcKING+QMXxS9~Bw_Pp-V}qx_g-)y+Zy z3X(Id2oSctwY9aayseEV)kEUtkE5b7?PEM_xEWv~WaF%4OnjLL^ljLGvc-(PxQ>xqVq(*QaoGSBmp?CoE?sfVCo)DF) z#V|92AVNWj%uihRH2IUg5jGVuPpQ-9h@$!}SYa6=eKQL?sd2ts#kCEa+3I(}`x%jD zf8@MP@WiPx8Me3E3BY^U0$NQV_Qc%(zMnH97$X0+_kBOHR?1*PfA@V#$}m?w6Y}xuuuqp7&5zSe z_53%W&${?k)6Z_6b*~Q+S)P8Y(?oYulQ-eGN=F4JO?>T$er6;kXwmxJpS}THu5M48 zQ!i%0?)uf&o^6=}J~ye9bibz$1t&%Jez}7TeA)M@+i&E4{`NliU0>zfDT`E#tKhza z$i_76KESbO%k7qbvp%f$6OTu-muqc+U4lk~KjBtOME5}P=h!qe2_2|__;^_ci5y(P7jim4RQ!4HQ7%;HMc*m?~rP1E5?00H}%>r)I5y<%5xmjjosV zG-Z@v@B?fi*)tftPUy4^icOXkrSL?%7*6@`aq`bXod{TB7G)ze-*`QMH5}mZBWa!$ zwlG7>sutv-Jh-Xf#nSFa&l5wFWUxwkzjQ?O;s*8Cxnkne+=@rHQ{U|Hkl!gZ5J8FZ zXme*f4yi4NBN_!_3&;c79FQbUo~io6@bQ9rDRU3Cv78f>*fgp=w;C>pr0uSH+v0z+ zw{cXY0mFM!slPOAk~jJmegy%*ZbstfO1T!X*%E7&J`t|%9NMV6Ul3*`3TiQLGTOmq zNz6hgsIb<|Ow}Hvd)O&mIpoTtJf#<&3FwNItre>#szQ904f<^;OJ_m9V*r5Nm%nc> z1MwE8z0)zzLs;SdSsd>=apPeU`s#mbz;9s+NzhD@5D{#Pp#L2W*$Y~iEux#yzhR|c z27*PG{~+Da2D)!+e{gg(ks4_41b#rgSWAqoy-202Zoc@Nqu3kH)FUIeffB$_x*0B3y9;>r~!@78ajuL}1& zWMqH4O|_Zm@&r$X(D>eyYiiXYpfo|6oCsHeEjBQ^3DS?=|H@4+PLGWVE1XVj&OWTb z{CUZeqcI*CsambIAKf{Wp9B{FknreM1E1D=2y^g}XfDh^<*2+)<(T|)gen(-G;Rlr z4A8V=uW9R6V^OCt=Fl6AUj(zyKw`1t#afZF6LVic%^>+n9g!XUsNdf~M?Upoe%Uo} zT2a&a(RAM?d);kM%4UxYRTV~~&1DK`QD*+zh}4>Z1RtcpF%(7(lnnqntci*u0cCf> z*p1kLj|%X3(i^6jb*Qu~0Su`QeHt9{UpTmOB?~$pp5N-H49s(!&9u96FAJrn7iM{0 z&fID)-j6yKaZ6K#!^KaSZpXIRb2(+ulh))`ca$-N6=WU_f>5#=!;m<3uB)5UVU}DP z2_JMf%&)WKzM9ge=z_|BvdCfqRoU$#mW8bt4OM}r+_hL3^$0N|;F~^YCnm}rj3i6u zKcJWEhQE@wFNq0vLpp{wpRVEyim#6_#D4}E@Bc9na?G&I%fA()6rN76!zprdas=Jc zTOSPlp&(u)Uor1Z;+$Ay3^d!b_01IXtia(mFFu?_;2>-&HbI}upZ=3SO?H$PzDFYX z1V&gq%lUYJhakMU(tDx%*SoiaU*vbh3eP7r3M(b=FZLf#mEZ9GoqoPMMc-o)@?T!4 z?lGVEDR~G#L-N+6=-F{a<{bC2Z_n5A;SQ#Ce_v{+)y4rt6V4>)(zO`m+yZGLZ9yz; z2T>8~uf0&MJsSxwKdh@@xf^}} zY(sT>^{b=bIi6n}C@t%!ytao;qm2~zzo`fBOK7?s*g8-Wp_PSI8; zhKClg7vK&y(HL^0C5EuW*{|WIiGXM9|^S(UPNLL{D!fm*qAmB7K?hk<}T= z$u|!Tps2x&L)?@=p2Y_ncF?oTb(9sMSDWD;pa1aFLX{P?C+!2Sd2o%mFZ}^cYgLDc z$QPX4&P-(dwJFT(YA`cs^ag}9m)AYG9HPL6u=TT0fu$~tQ z(DT>{qU&sFq1ZKA-oO37yFL7s|NP_lkH$XUmoiz(RKt%ZukB-ne#j>EV2ZnvYq%aS z*;y1fMM_-7xviJf`adle3cA}Q^{m|I`S0N&7=v&O(`RL^arQecN>|j-xL!;yK0kM3 z71@_>>Qp`#d7TzgUsS9SBu)K{Rb4$B*LSW}U$0gAIP7WfKrUBpk`qNW72^B15hL!A z$s7#jL+GI7fSn5fQyv2XqLGL(08a2O3{o&?UC`*h-;lqB>|^Cm^GigA1x$5{LAOB{ zu$8tB(qFkBQsUFu&fC)Eb?BU=f}Q5%d@cFDWpArX1NHVYWrU&HqLNEuIE?kK^|5JM z=EO>vSV@`AyXHQrxc!kMM}K1iY>n2EBYLRuWPAeSn+0Fai@|wS)m6beLV_PfU`Cr9u*fWjL$c| zcE7hpo^~ZdNg<`TG53L!UQQ4^Tc1C;oK1Ip26Aa#b!4o4PI@?nS|~ zXx|dSgDD>}2)!H8g00fnLM^&?)llPTU((eHxR z2+1Qr8K@o}D*wAeGLW%ZRbg3akWpfMJ5g)1!VoSIOemcA7{banDMu4JvL*@s-hLrZ zt7L&z_Ics4MZSSNyzdw&9jguvXUN@!_f*D9*&mSh(CMD_eI^*520y;V^_MmZ02bpX z)?k?6n_Bk65IISh?%j|)NM6?k-TG@G57nT_E%kY_JV4#EL6i-Oo*-clRvGuRK0*C5 zejXf>@7d4CQrH0k3N=Q8o-jSa7PyRnZ7`5Bg@idX*G{KOnk}}=TU*q*SGbLXt)k-(#Vi(Txemi3|LH%)6c*l zH%vM6=on)ujFoeT9WNJ~2}?;$v7GBb+_*p_s|mUjHc3YF==~5c0xGf6l=c+roP5j* zHH&vSftUL7pUKVeU8>K?=h@`#&73c<9lRokfww(3g8pY11I`bqHsP5Bg*94m@{iYN z-@|iT&V}CpX+CTs2s}mQeYQddULz6-e< z6+c|8K3v=T3+)GuRaZ+&fwllv*JNh;`8AE5B~=wNB!j81WAAT<-2Cgtte^@Q*~O%t zYI3s5{p{5a_scZo9spwunqes8Ta211)N6x=o{$fET%vJ)JE zrN)r&ZjA3^FYZ+wf}em-21bwa=(5qjVk<8bkl`4!Fr8^sS?EN5D28IuvZdaM2UtjTb4Tr+@t&$PK-#Za(KN>d7{x^hW*lQ|@h;uS0;qwJE*pTAnjYJw@R`(U;A= zwym}E=QeV)^u+pj+~WheL~nH>1zN<+3y1ZaXDMGCDRHal2`bU6Z0&Z-FJB1}oyIcc z!#K2%koH+t8k$bz3}00Z3^vS#UNXoLzhp#jnTQL2ZR@N4tjtm~H==e&fBan*p)6`) zl4CBKqn<`Cam}e+Q$exXZLzvzu{5WG#Sh;3b3F;DL}PW_-cMfF;KxYnpO+141#d=u zP?zml23F)m9GUk)2wYSebngh+$5ZYLDZ~y_I@@h(uiPhS4yy2Xn$Ux&pzIwxawroj29PWyL*b>g|r>H-*_V%d}m6LCQ_#iLKpunV2Bzb9TkIrU$Y2jgc( zQ%j+Hqyc**S-SuS`_?;cDRYLMuLVxA7bqOV`tEa3%zao z#Onme+~}TxIQQl8DbprS0#Az_qmiQb>J$IgT%}S5gxzR8>F{jms7x)r?b10XQ_9-) zY0~`Yo>vRccgi~@jptzn?Q^7@vmj3?govGFd+aC6w#pO_JS8t&lU;2#?U}Gv9NHf_7qqOTM@+{|{5%e# zo$p?4!J>@VQTi&AZAEC%>Al_wfd1P(RaEu1dMxF*z+aFO6R=E%H6XRW&x7VCTnG zK7n|!vF!l2F_l>-w3&Jc*qVxu*fq36gP~qFMK+n}Xo+CxUaR;Lz8tNH1(D?RbDWI# z!u=YE&+UTH;fO-MWH^?f>u@&?i>~wL;sv3*y;zJJvQD;4XD$shHqZL5_%&JGyX40; za*SX4b-ipX=8#=H4;Nhl>ddF5YHPgDr~Thi0Ldw-*}Ns!&Ur63?wV|%3ct@?dTA1T ztXV^Gg(ULN{SLD=sZA?ZeY3iI1AVucI{d7F?_t*t45t%IArrjjKz26K&{lkgnA-~* zE**FTD{pS`PhSWQ6bLP40=-fc#Q`chWR~0*33u=;hfvuODKOY_q}cDsSpWjB&{|J5 zXi90#Qxz=AfbmL7p~SeC3DYlY3yG0YmTBFCL*F9)ST11+i~Nhf@hr#3G5e*h{3%o@ z#L>6&;P-8fR>hx9YT4!-ng@>E)FLJXPUGiB5jqQW6D;Up}9z@XRBowfR)GFdWSX3#MO%ttbf zq{fP|Q^Q08bfFV;`5PHjK=5}NTWoUQiPUckLMqBXFNtA*2U)>(!23K`QKh-Spoubc zpT+avC;!ZUT9ekmz`Zu>9aqV@lDk4xo=92o)jW@H8*W@*c%$I5lBhY3$6tSG7XR#j z5h@WU_&$EAk;CC>rJ(QY_;dyMcR*A-3KH_(N?2)c&$92dyPN3It$VCjy1%UD-`~sI zeE2f}|Hv|A!Ir7~wlukY`@Pw1PFiLD=b-k1Jmjlm{x5ooDx4j5=zAKOl067@CaSkydGzrLaDF7Qd>S)Di61^XCDk6!9 z0$8pR05;4<`5$JpLj^-edn*B;FcgezxWu_$d6Uw{xPFKfC%`w2x_6fTjL4)alIMVq zsFRBhA|+(dnA4na6p0}OAx6%%im+vLE4#WvNSLNUY)YNbpvh9ObfOFe&~u4^_2u<;==?)4*DB$zfSTm%64qypG~6!$XxWHO!oZ@lspU^EVwWPNf1 zwKas73m>4rV1HTRr>m?Ey5jlW*V&u&WQN_$M!CUh@kXE`jwl5%v}ewC1Wgm|>9KHt3fJy({dHMe0o&A+}dANaW5 zcX_o4-egR_z8#lBe5^W&85o)AnLa}WO#5E^Dy9h*d^;^&*X)37El~8P^a z$E`K8x5qK_G{3qRUqIQ$ox$D3LxrHvL-4w8rCS#QEc*1vlY8yE!NQ`I@^?K^v1s>l{Jz~w+mAZgVbED36U~@}+tX~0F zmB<{1`~v)4VS1}uWs947hm$5M1FQGZd&ef^wjFC4aM=#KV-YN3t>}Q>g82^g*w2mM zul^16yh6TUr;$C(bM#nU`%LLK*&O$$`M-3~$faHNMV0=hXA!C?X;xH(P)tX^!@v`B z83}(E={xw=@)*4TZuEZeZL_Ab*8i~U;j;VkaA<#IUC^3Tw-K-fi4GDjqu$Z&0p2N{ zlnN@4x|YLUg4EZi%AVKL9^1`&4w6>&@^*dfO*X|dx|K5+MmviSx);bu8TTouAapElbnXupCnRxhTG`n5oye5XU|9rQbVf!7CPoHt$9-9I62Ka} zwVQ~{@$l~~xS8duYW}xdoJx(1UT|qiSz~k$vYf0`o|jcW>*DeGe)9;d^pcV2qe!K?pZ5#aJH;rFRnRNN_@s0FXe|4NHP0(QHm#ngjyg`!G zBF?52+gm-l?ze6}eyw(I8`k_7z$-5n4ciyaBvN zaKC(fdheI9JNgMerPVNQ5UXbGN1V-JS-6Yb&BD<*TQcN6wutOBH!4St z^E9gb{v|w3p7I26-3+UVM-(-^8@%4Uy zf`*DyZiXoc;av5O?hHqBRhS?H@Gb(@Mgm%_UI{2wFDnK`S5Z-<+P-fxgm z+_kin;`Ge)t;YwtA;JVE+>3EHOw~GXs(ty1hk4rF-Y;D5glZ}(YPywe!a&$b#$FRD zb<2oba9KT=U&_l|;F{gKf&#$UAdi;VfOiZQ65+@sK{OE+cd9qtFfRQ9sh5l?oe~kw zTslZG)+cX$iosCH@FFB4a;?vlldbUf^_wWhh!g(WS8LqY@z^lo$L(CQqpYm1(_p;7 zC?au#_qqLOP7dQLae~z!628YkmZF;O?mE+%@r6qE3#lYr%SdF#2po*hKDV>|A~dfc zbyZ&HdmC|PeDyE>+mw_#ZB}TdYfnkqZ) zgtR}0b-mnQ@%0}HeLNBMJmf&kbo}>EHs=?X{2Tl~kVbr6FS~tpD=RYFBp~=_qUUX0 zhAwtz@`5Hf7P98lmRAe(9?K{XneZ3+I6y#8hVun%0OXAS;jrfBN8yukVpU&$!v&GM z7@6Ikhjr@`pVQZKKDX}o;jsS&-J5|0B5;-)`cPW%jR;HY6i_f!U1jOn_w=@E_q>8c zmxhDJz(o9|=y4e#ty2_xox~>~si)(S$;>F$F-QC^YDWSN#ySK%QmZAlM6o=yO z?(*H|o%u57Pcq59GdcV2vt_Nd-T&6}P*Rd-pc5MEXdsYQn+i^&H>wV{t7&K$7-;YW z_b*9W<`W5b72a$(v!0Xw0!6sb23 z&d$2`*hI+kx8&O3O}KsRsTZl(oA7n3d=J3o-T~>E;q$Oa95K7NdFS3lfsw9i9~;V6 zm|9i%vnUjrdPm=_5te0i8du5`Pf7u%{x4K@UN}muQ6qD9t0OK^*nMW1-*Q$JM*gF$ z_{@MDI!u%Wml2kZ8s$c^p)Sv+zzo+R_G_)S*oQc^5#7B2&1dQYpR*2!`TE_6`Q`Ll z9jLw`C8Ba0V;~w#RpF26#M+k2tee@+VB)C05CsVk3AK=;s7ln znT}iPbH3Q)@Z9>-i0sXBO>p1vU$j7DL2$Z@-E91hA%^9H3~jnQezq(}h$o$Bp1!zX zdb*~%G0;wR8X!dXO|vU?^3Pe6c0{8XftAF8h3ahJr4gx-eDg0d5U#8C@8Ew~Z(ck} z>{d21eBYbaRhV7R)JKl?b$P zjy)XIeHC}vwD;Cr^AwS@ynO582nucL4c;5)^Xci{?T%#;3|ZxY^uATBd_g1pVR~uN z*!5Fe-a2ofqn4$ByL2lpm4)}5OuEp0t2mmJ|73t2aG=M%&qwS$@1}c&+|I`DLtpB( zw7hO_17qv8=FYoKTHfM605*JlMD+5j2C&YFkcT(G(0kio+K*j+o=_EMA*I~TPReTaTc6ADzn4eL1jmNl1!hLt z%Shkc=jL=Y&;3ITl;#jR+nj6bYu0YxZX6mcQFpcuhq{v);|OPkKTGlL>Cb>8tx zPWU#16k>bz)jb{MroU$zall~mI0ZKC99WcV6Y)5+Fp~C*tg(AIt6ffQiAgXGzXi?G zOD%nshsP&2(%0nhJ)+66;l@`D6@*E0JZ61EO?7=keQizkpKgC?FSyLc4{a`f0aw&C)E|2P;0~F{`wa9u znt^rI{MYKCOnye!0+kXVVNdAidO-*yAbQ?V2Du-?`(Tq%;N?4%sYu z`T~y@1(SY-6sTR7WNW(E4bktjEMYp7?#Nt)R58nb!ep$~4_`q1#Wh46@^aY%yl%Z# zIf=NP>xhjmk8)bfp$wax|Ft(k#3f0&ArsUm={x5{b=bow8U;?4B$9{QEM`@dLf8wVR+L3sLAzSu6 zq2j?Heg<7apwo`4-z$*0UZSiav$?GUDXFaCnTo=k!EN^Bj+YOvdbvDY(bm-f`zCmb zz^0yROQd6{a%)e)Q=NLEz_-I8ZWC5xEyT?CT!dD8C=$Z(nEE>D5FFsYyU_T4rqtMV z_Z$Y3xMQx<;Cq;DBE)AtfCTnk&T0bQ`}GerF%8EVcU+ZwK(bBijlOS{8#s4_f$^9P ze^{S7eD18x^WqHQI={1S=weeCxblR+g*0OM8`ylwr_N~MkKwOqy( zY2V$etu%=Oqu_mlS78@-#gLSn=W$&1UFg2=xj5kjqtckpj$pR^3%o|RfdpQCAAX>A z9)IFl<9%Npjmhd9*|OA3uxI~94&5iw`>=aPvcm1S>iHI>ksgfl*9_)3x{7;}2 zrjMk_`t>3q`+9F2KD`n(aGuSf^uq@$*Oq(6T&LMhQ~s$$6?%I5cI%-V6BgZze1`_U zD=^hf8px$%#(bSfHIm~IYZT2{Qf`|@ zMk=lHg%Oe7MVTG30|Vz*A3L1J)DtOiaINq^I%q=dh$?}JSTZN3@2Nl0wlY$T(`i8< z)%IfQ1(~~qG|oZSXt3d9MxrAf_*|J!riiS8Ufrws8-fbk(bAklFaNd>-JnO1M7~f$ z(8G6on2Tkoh(Dad5nr~m+9z6NpVE%mkfL|ft`cK415yKc2`@!%kR5`!-79ek%rgp zXl9U-ErYH`ES}G55J=ya7N>0ty)U&CqiB^s^0?2s3fH@j9{_b80?f($m-zj{_s^Yt z1^!1%m)j8N(9W}-^S8p+G#CD$CtQbx9(a)SuCK7o%s?`g^hMCHrX~=Y$$)i}3fI5c zpo4h#c@bk(5bv8jGz|=N4EftP4pKZ=+g(q|-r4MTWRZ748!I}Ki$)s98YGw;CgiR= zKbDv==Vs^e(m$}U5E*Lfx7uA>gDKCu+@wFvW_}7n#RT@#SXI^aI-cjsiREP;F0Ty{ zv+8IP1M9b$-5KY6P5GJ{$RF zT-B=0&R-V?9g?adPQQ9RiPeAD9^$)b?3kEI*T|{FezVZRzcH&0IE$+wD?467rYfqTnnTrF*-rP(H4<`*~PNMR_E6lmOB8?N|m&{lAWD z1Rfwk{nQ5jJ;KN1)(=||-P!6C;C`s?a7xl-Lm_?&A?a*&fr*q-AWcUWB^C+0-(}w% zobtr;j(|iCb~B!_okpi@XDdoCXBDYsZI{e^9#!&#q-9^vAAX--f}QYR#nXu zG(jR>`q%z7ijTF3ELi-Q@pFf%!wO?VX2WX^(ttqHvW21phk4Nz|NmnFJWC&E{OyLh z8@W9L+S0OXI@5mCK-Bj)YgkYe9r#VO@N1Yv^2-V%H||3owj&b9LjsqsCWG!g=<<1M zI%`uIwQ91ip#xzH*=Z3u#FE6g^p1(cyevtepfFc`?q5Uv9fEEf>b1yv?th)t&vmv1 zt7>RTG0-`aXsp7~}XH8=_f_vpBi6}UEAQW}l-;iEd=-gwS3f9YMp z_7!W-$yL9Yl@v`xrcifRPw_84pZ%^5VCI4pjg^J62#hABy%-} ze&}_PmqMFw^J_l$MhpL3sW736sAMFgqTqn@aLbu_WU_Ikqttr%E<8Mt3qBAv^UOoF zHt4;U-V!~H$m;Ljgj6|jzw-3T0%_3ghYuldVEr?*-10ECTad;*K7h(N7Bm5ra ztIuSV_Ci0MIU{0U6b3_d8-JrXKOHqhU{ks%S{xa^w$t-b{!a7u@1$KmtNq>ZnU;;s z{=pVTu_u~Cc~6a|QVp5a_90%25WzP+ucfS83=OU4bEXe`wra0uegy0%k(j@gHx{hQ zX@KSQsd}{G*ylByQET`sA}{}Bqey9&$Wy?8HU@9P`4m(%GEUOmgW3E2H;gO{P^cFR z+ZUku;Ygc`DP5{@H}Hwh_gC8W9oGp|s0LGS@MhXIX@EHeUk>N7@;(^98{g=qzHL3& z=Qn@^qkETFgbi(JeCq`^+?{v?enV#&m;-sts3Nr8j{*rfiYb@$d``4Zrh*uCpInxM z*no&DmNoZo7CSLE7IsUU1LVrRY^|KJ3J555`X0@aO=MH$<^kiKH@g@#&hi9gF319U z^1LLep}v@Xw?BliYb^gM%e-OIZuoijg~-9@jJ)LM)6>`QmG&hAzx2Gky!sI^O()3S z``Dey5lU^9Qj*hs**~y86#1Gt&O=L|kv7oj6t%W`Zg2rrneiEafQ)tbP!AqsZq&L0y$8wG2pGPAwrqMH0FbzkF--VSUl?_#Dx zr6bk3rJS)qiMkheB`Ne*8QTo5%V!S8xU6U~rWR*q!^jdcCHJK{6 zMT~X?sDA4`E`F~%U))PRlFq1=D!)z$41hs{SDeF|hBB@M zx`A3MI{iD)m#S2iGx|}(qpV{dU0BdvS@xHS+{WSUoR;UkJUqR(2mQl+nRlyK`-!|; zfRMM222bDs8wfRg8o}r}oRo@O4H3 zBnpZD$cGh*$6`DlxOqo5A?`BT0hkR?klX)~a%d^tkB0RC+D^`ebdX~s79<&)b3#&8 zM1m&b4&fdjKStUire~S<-!%$xJw+u1qh}0;GwbstIu@{M|UI|Vua{B0wn`6;i z{hKrNcHXuTZ#hq&&FTW(@qPR}nsX-8>u^A2O}-hzX|{;c&no}^NX31Fng*Q!Xyt%3 z_rik&C!NB>ss@eoK`kLh6RqJI2U@qM<}#pv4pX)no&Y~HcIbPcAzPaFAlgOkEr(Yk zmb7`pqv(h60pubCPfYR!OeKxhyWdclj`x9qiBw;oYsWwXQh0fK2)ekA4)OlA6AM&S z2{m~=kEGVO3<$8}2>7}xD$X(tTwr@M3V3{IK{&YRpZNV{gVmNw1{FZ6>ifL=r2ZQ{ z*FB%3?xDJTk8qrqkB=AdGtzL=(R$9Fh*~CTB(c8AjiNx9sE4pw_KOkDG84oH{F-b; zUak=sRYXdwn(KszJ7h3rp!W6F`t2h6q;|$s+~DT}mvV>F26$0SddA2d#oJL#(xAlftpoMg zRD=IW*Ewn@g^m)5&oZCae)h_yu)eNpgp4IzR<=!{!0t|}g_Zn`IaZ@@5`wCM z!Eqkb$RwGD8oS55<3yrp7@x^+dP&SJ0Rqa8ckFgu^pgx8#uv6&LDGKgtCtsUW*iFT z^rJGg8p<#X+41f&B*F{?v0J0(yT3M3j}O|gmGb)XStAk{&D(gYCIh8U)$Gk0TiN&< zwcG*^Lt8aENU|E_{TrXg7rnTHoXx@xR}>WUaZneRj%thKCG%J5zxEykkYoYD2usQZ zLqZ{keTb+q#rG4zog@k2s<%)Jc&Jd2Z?6a?-MN&OK`lDpP7?XZ#gXPJ4ia4y`{t0- zinmjG3lkk9$533M@7+U;&Gk=#x)d)-G)xn}+qRv-UDkjtHTN=nMuH~?XiyfHTYp&$ zcH{`G7(8a7;Zs)L(3rd4$~DctVvcPbg$%L4kf_<($x;#C3fLDi5R*!)h@pT_3Ap{> z{&qM2iA1odnm@C{X}-t)1r6}veimr+*vFm*#!`k6y z2+r;f(jZ+b`$69Csro&FCt#e-D2j<$fkNJ?Kt^__l*UJJ*CS&3DC9BdQZS2(lK1Ul z+up&U()i(%-NFbaRff>*&4n3Vl|gCbbFoWZ2n!+zXt#CUl*BSB3j`@j4FZPw_F$nf zR1hq^S;Thr3fQ}d%?$Mt96DyX82=j@1}c?>3tx)xvQ=JulMkAjan%nH`Jk_>dlXB#!kbm#2`D%`IX$(gebv4=1#n(9*0e{!l z;t^)VmT&(DxC%5~(4^6-S)-4~rT^1IY=6Gi{m%0qTXeN-iwb+W%R~viQG;8%zsBzK zPFMI%gnD^Rxmy^ppS>5$JH73m0$p^X-E}j6EPbPXf9Wu52wwy_E>(hX#8fmibm%}e z6bJ^5Vk3#A!v*{mCFW~+OX?Mdf-JrXsLZoo=)KS}5yrBr)POTsd zwFUS?6?2jmq)&Qgt**Gs%Y*iuC)J}SUbE)yk3ggJy~fGDqK|w1ShR3F_wcu-kY3Ps ze!Y;2?O3Hl?V6T;uo;<7qleDz_vxdIy_@gCia<=@^OY(Q-1j?K+5*~Qif;_blOtPD z&N~7T$S=r5-16#nTDEB~#%>j9|1K`lOV=ZURh$376{6>YiPbO)GOWz8u9#6vvr#EQ z{A9#7aZr?8vce@8i?UMvwxIr55iodXBzk$Uraqo@L+8N^iN&0~D2^I~$1=<(GA5lN zcjD`{l=j0W$}#m73eFnVUD@!UMG-TmYhh?&p7{CVM~~&h&%q15v6afNm#cisbCg=@ zcZ673(4@o%2W)2G10^}pBwiCna!(0k1hG)WJRB1$?jcG^oGi8vc#?L5UhRZ(D_%O0 zm>%=iL`2)cg=HcN)>N3qVr0`u;J(YmIKx}wBuu0TvJU8&G{raKQtk&2H%*+oMjJUX zj0;84HCNH(kIp|Te&=K!zGZe^!+J2HXL_Qc(=*mNDw1m|ShK27Y+H!mrV_Q#6N{zo zlcagb5B1+G5-iCiWNEoQwUT4AHDe7?lT;N_T?JtN@lNfQS89-Zy0B@2BoG|nd6||j z$6aGVD%RFUXY$t8lHN*to*L$shrcz=={$FoAE6q~r56-wB4{n!RjZdD8M0@XV-#F<%TqP`{Ep#u}Ltn&_KiDHkqY)=$Ae zzM-ia2z{PS-%WAshnlaK932%XS~&ZX*!!N(t4bgJjrt2rU@KKaobubJ5eXh{SUh1y zC2Rz1_L<3feMF00;>i+4S}MA09|saB%!fFsuw;V=TA2@-Y9MrtKt`tgeXwV0xk}0U zpj!CsD+G{#TGijlG)t{u?`Ot`iqL zlzF9{KyL6VXk^(|zO=I3Fb(Id5RNKU8{Z*~O7WwW<{qoFK%q6q=34S5l3s0qd;z9i z&cNK2V6xv_fM*f^c` z(XW~$&o-nQ+H-D4Nd=C-5yF99X}NtAVjm_ut=m`Wn0=Z}W4Su1T^+FOS2SBJ3Kr6R zV!y!B?UQz{LUe(PNXq3RjI{g6E(bXRlIK<~ zRIF5-#v_`hmWD*x6N|ugU7d$dXt*1Q7W0v`h9&-6xg%39| zy0FmasmT89TY?99D)qD$eNKWo=zmjBAoaq~nV?9#=i#qUoiky;mr|5?WJsJ+ZwrI; za^W`U72v%G)*qwTZJqEie{iE94i!=Dk6bcfk}54fPU6NGj_eF)8#*3~H9k zn%{iv@{6ww^Gr~*_T#_t9rdupGVA(!fu)9FX-O|uu8^5P`Bk<}@za^&nOU}4%>MVx zTJ#VC1yl_M{hTp3euJ_e|8UhHug;&ryy~}>w@Ef-z;1#1&~9`9Y%re}dBf4HPs1Ch zzSG0l?4C$N{-w@0x|i&8wU7;s@}upUJUCFD$9O%~OO&8aQhD9jsM7Du=TEc!?bMVh zAD|-0C17D?VdwQL)mblF)-Is?6ig%~v0(|TjprpOFWD(RQvT6D)>$B>h`LuAT8+_? zG>vu+re&4*gH%#Y=(~XW$i}qEP-IA5Ncu2rb zqDdp!XY&sY_yH=lrrH-#+i+9fn`&^`+s8FOEHTn`$s1Lo`KjFHJdb&72~lzkG~81y z8_m`74XI69^$K>(iJc5&@5_G#NpxQ}#bB`s3M{|}fhfQA>fzHcQ(E-0v3_1#fd{9k zMP*=wAJbJyM{ku2)*p-UTaW7|_-)ERDK1Tszq3o8v+N{DQpHn-yf1QLoM&k&s*u{D z4Si$Pe4}2(T!xX?OqO4UW`{p$@)s(2E?u6iOSAeZnHod! zM0uxPeV&gC{w2o{Z+}OKx9A5$6dz<7ROB^);xS4}GlYEgOGYdSDIC>Ao^fMDfR%Cx z=TkanpG-3aCEc~PTu@{`tXYCLmJV22RvhogQr)V~B9w0~5~Ce3e+m(S>yWDG#QNuO zU#AgR+$9+Sn{d+e$4^%$%fAm2j(Qj>XDri!%5W_d<`|Y`pA=QN5dVtBVxT{5Ywi-P z|C-Q;uq3X@!S=J?u8vqYEg$3D#q2awUUb4ruS!12BqjuZDpH-qyCLP@EkJ?FxSNj} zhoc{Xg*0)0=*YtRK9)4BzZw<8o{qWsrS=t1>_u`lFwB1_OLCTo2%ZG<<2Ja?qpObg z>|+G1WL~~fc4D|`x`=`}K9N0dmnX6*&1y-ZUzt;KM_t~x zo65(?UN{Z**SWj^!|KjnvU}}0n_gB1*iec{i3cw+Q(kKNc1NhP5ely z-%?0|Ns$B8LyA1cgG#0-E=0v_4Dt5Fkv)6~!a*B>N#vMru7>k;?|3@k>tj;}J<+T@ z)3{N62O-yA^e6Oo2P2jo;kac5;>6U^{11jAGo0b0Sjig8b0rwYl1^N_5|aDOg+qit zJOoT!QLCpIZzzo1ds=;}&v){06-B>-j+X5u;$iJ+d=Zi*EVonsI?c*8_DLX5NSbF2 z@qf|ofoOj$VJD`)H%tw)cK4Bz;O)$|DjN3Sc4#6F-yd-!C#8fX{Ov5#0Jl=z442rc z?)=Z9a0Z<+wjy_V8o!rz4@NCm`Yqn1$65 zdt6PTy|=l(a?M(jjB*2d@g?=gFLvi&+z2 zVoKvKw_PyQDzNFyw>#)aXzH!#&4S2y0Q_z9{1;_>yT96*88(-;x4~@CRgW8*}6hB%?1^i#;4pMHApE(bg(tu zUZh$vWhT_gdzdw$PZ(s?py*u0;6+=g8{gHGVk0gn5tyij^8RVQHIY`R>r{!cO=5ZK zE1q!*xi-Sl4%(sgxL-1(7MaG+@FUUkmoi*?x(0h2nab;exxJ9t=3*FuqeAfx+V+C$ zjn9YE*Fkdy~GnOuHe1CrLxxrwN36@;~&4$(iQ zD(G)x9aIwU3&Y?<9{pqTkf!ksJq=qvLa@Wo*z}+$`QKB2>fkRx!L*Vh3JqghAITLd zk-nm6LU<;+sLsY6-y&{8--ZN}eFO1QW-+>N>15RAX(2tizm!@7M9ej;O;Xwa>S@pJ zq?+R1JSIogbwNx%if#ohA&Q2Gnxoja80#{@+E+vcja^}e5J5m74nkYU?vgU3&F*tO zDJUITPdhfj795UegGZ7DIwTdTS-8X0+H*-!@j6fwb@V5(LTKN@Ri--eGo4nA zq1PC-N@3l6b!j*hIxJ$&yM3kqwy6(!@v+*sMxD5*La*vv0j-*Z)H>5B2Wq@#{>pT3 zdk`oxgDC=xrA0?9wu-GB6%&TQ!~e?NdpDF68hfRMNQJ?m$e89Wm}{q{RX6MIo7ZvEdX$NoOVQ$gAQpW}*kH)=Ktqf};tfhSq&yY3GDw|H z4wEIwm$jnB;x8QqPxZmBQM#CmLl}3*#w|HOWrVO~vqp@+YaDEwG7PgeWtGaZ5R_DN z;$*=Mp^CJojF#J3a{8I%yd3;7!DzLS^kjO97`~`PIo~|dWjPoKl}dkf$y7dSAJaj< z8iBsE=Xw#Z?!OBkX45KxcGFWIc!fpA{FY)Yqs2y$YG+ldx;9Mv94)vvC1#ry9+G2i z24tjy8Jy2kFzJR*;|EcmqsLtFx+HHsElXmED6sRIGixWg%Eo4ENEJj*Tg2=V%dxyh zestT^47b*z-}}|hks8vtSlYR?N_M*~lOQsCFZB-cOOPZe0Ws4;N)!`)0@auoc!dm> z*!JjGn9f!)_#pBAiz_|Zlj0^s#%{Fc^X90zMly5ps~(iOEz#y5*9=bvSWw^>M;OqK zXk*(WmR_U;u}5UO=l30@kFeB;m`|`5{#4u@v8y^TXjd!*Vz_3tMscx+Yz?q|GnQh| z1T?k^XV7cbu#D@enxM3NC4XLG=E{Jf!`XAJ?dq`73N|ECy6*U7J?5Wk53TWi+T;*& z7L*xRO1L3d+HnC=mx^(rC1;gH#{)47UBW~Wuiy;n(hA=UREI@kNoY|E>d!(h0_0y< zd!mq_t4SY7K)=6(wkDA4JZKS#)#mk`@V0%2>RO;7o@pk!g?WaW{xh@}d8BBsur#Xp zDj+chX(v~PTYOafs1wWwLMaX@D(d?2FaEri^M^7jI;Ad{wHbkSw@2t~9fi~$D~o|J zD;Fliv?wQY{{Syy!xSXyKBsTEw(Rg%II-HPy@u{3w`7HBit#h&>CYb&PH&|R-<6*b z|D)>A#^AuTcn|t6lV6M}Tm>}KWZ}^FlrD_pSQE=|K}8c%<~VZ49z;C9_-m!qrMB(K ze(tC7Ts@{3SJtUn=gnuL5jcZN{OtYlFS0ZtA^{VILt;zwEaFemlqC^yxlX zPMMp<b2 zJ^F=iv8Yf;aX7k>n@kww>9e$G;>l70U)H)25o?SqCP( zyB2_8H?MI!t7r3YhM++Z-Y;wIWlFI*x|d)?s9Ct9Y8DyauQl!^PS`Dp>g`ztoHH&d z=O)xi3L;;o{?4hre>x_5cN9=%`Ubb(EY|I?nWTyzf_Od`1QfyN5Y}w?wVf=;>yeM- zo&~G#5MU~TmbtyG==Ej(G2Kf@40BT~+^2mk0#fY5ttuuzdB0t|tyY{12h-q*0U6Gm z3#oCiWSqq!7H&g)>1UD7R1Va7OCM1;@I$~=I5z;X=^|iN^)g+>W+||^iw8H!swvax z&)g{S%UtL!>hPIHg`|z^g}+1!6<-Ol_oX}u*p!+TfjJPFF|?!Hm8nha8(8$MiXg2d!`Nowz74xh^s@yZ8$bX+dMI-w8`9bt7lJ2o@mJ;e*5TP(s zDE>|6aS#X9RiPQBWn0(piZIUeT?$3L2}1~QeTWVTaq+@I@Spg^T}259SpKA>inPYH zG4rvF(0Pb6y^^4oh*dV27Y!NQTGe}r&PVxBz@{Q0hItraVyyh(4HDRQmXV`qj8nsj z*F5`k3Pk zqPfN*dL(g zS-Q3Vj|G7ERkZUMrd5u*>6S;v+3{D~h#w?ST*MUdE@0{A4vwhdYLVY0bm;CUAuC(v z4d4G68~dw8!@XBM@pdy5@-b?}H)=ZM5?v@QCBsP{1A_qJ%KM{UxU%4Jix46^U<$t17gaGK4FQNLBN zdYGndQ3yq8C0z#Z0uj;5hSst(khfl&|MT3#!%rhL|~QhseLW@(Bt zssHp%>$A(;r>D3l?zN-eO2-!RnUB@}U;Y1?_-niPH66#vbYK5*#Em-nBmwM74MYpa zJ`+3Z_Yjlnu?^liwXNa?F(LK?thVrtqSYQAOA-F$j~6_kw7UZ*W_yY$ZWd=|g3CH) zbmvqa6xTc)h6@DS8IsAz$yx^nII#;iKT^dTCI`s+c2-eX6T}5-+fpvlZD_)KAr}LRLH^*^q6_%&%LNzi2)F+pMUSS)RvIHqT!=0W!{&p}8BSInAaVR0?9{9f zK1xxlU!MJAo(S%-S#O9^=Ao1@10q+Ab(0EKQJGODYOS|Z7rRa06u$a==PmvU+O0d< zhMXwcugvpVq9}6y~neg{`XnGyL%z zj!xee?-qLlAl=I*Ct@PeE&F6a|J7a9DL%H!U*dP>iAHspC4@cj}5r)au&=1=7CM#11WA$jK-o!Jc- zRJdeWY{7M;>wJ`E@j>m#=L3Ge*+J$Ov^>aXaZOhZ#aG3+e(KqDG& zEub19DzGQ|*ws(#(O!5&&%&+Pc!rdA6LXLC#lj3)VuRNg(E4@1Vt{O)3;l{=~ zja%mze)2CHIX9)hhd%s}9;r~l!5SS%^qp zK8mu9{Wz93r6F6VE{^pX4F#$h8vHs6WRSN>=(4AE(h9RoyNK77zxP z7`AWlo5$Qv)tJ1r0Xo4xXn}qGm%dZUrWU)9uu*-Nr{)1GA0Zfs}sjDxXRsZ1?Lw(ND)F_$f>XM;pP9+MCnw&{WRE13#cgg~!6{2NjrZQG#! zm>|;#Eb6y*49GM@RGyaa5qT_cKZ9{dX5IxKQIwd7s6qz?^&F`xAXIaQwyOC!nP%cQ zRk6+!!@S1|nOhg1s5$@W!1m0|Zzj4iBTp=@Q2K}fbLShz!yF&SEMuLTS1q9fEuk(1 zz={>*$=bMUg=nW{MVFiXF&mC16P09?*1i45p06?fLkrj;=}sra8G4rIE0v`bm0sC~ zLc^+!B$i0^JBtUVrtUH#Ld)c#$KrH}vU^f%PZw#AQG{OHBUfjH%cz^urXq!hS}egj z9Y9+-u`+pt(L#?=i{Py5uugv#nr2q1!uBiuK=;|E7j>NnPdGb1#X!JjFrM<#K^ zwK-@IJTU8XL)k}ZP_j*aW!yV|ZX1$@^q$S4g)W!U@nS{pKb0N&V;!+jmJAI^3-SB6 zO0o!o_qxQ}5@5W__kUMv4Z>VCQb%+agdx4rywVB~-RRTk&WX+GQ>Q&wH%=6S3~C|I z1BCLJ=Mwl8+^Zdu2 z^EP@eso~BWB%(7?Gq4R#=DUf)*=fdM3Yo~6(>u^0Pk1Ib>rp%P4}=U#ArIdCTk7jL zTbJ5u*#vXQ;#ioSM$q5oQp^8hIcLGfP5ou?HVFm(DM(J^eijSDpg~!jzB}Lbgrp@6 zWw_Y)f8QNqR&4X7w)N;RObLE5eqr@KHM7q7jFHfD1k!C7F!82w6iZq5YkG8J^3T zGw#Kf#APk}oDIwl4!E%Fh#V81%?gVYJ@79m(c!erc*mgI8=!%(I?F}`UV$1-`#vG6Iq z)M;;O$#Ec9H1p@DWcdDNAA(7i#63I0;wVkimO!`m6(cJT)U%ftvC6t zJ)+1P>rHUjNllP6gy2a37pC6r3-Q8ds8B}>JWLu2Ay7=4?7AAZika4x!M-dl79XX^ zFRqT%(Z23K)As$}MxK2zl9l8_LGZr@Hey#b+u#B-=n3^CcMQ)t)E-~R5ve26J;@*Q ziQxpp8so4L@G$+pMGYmXOwnSe#QxED*f#EBd(9A&R^~unRpi!{qbWs0qBCn33O`|B zz*QKR5NZcRexLS;laW8)`ou`-sS{JlXaC{J^jtwI-j-uh(F?D*b-#_*rk<4}`~GBU zLZX>3tfk+v_+zqG%c<Hf5-4OHXGabq0Kx^#>n2cnL?J3^ z{Ff5*VXk&l>y1)9I~nF`6iva$qHn7rMkTun=_toIKUqdjN$9pxA)P+R&jb-f#UH~FYiy^5NSYr<1FUKD#VHjq zJ?6@VFr(1@JsV>5Pr$XJWn9nJDnFRREpt6|7jVr z&$Yj5KY2)SK|?@zelUE!M01hXn-N1X8J46@xMPGMPK;O4a>YAJt|R)2+%Kt#7%D2d zwQv4sXfDZADEoS6MK(pBDb9Omc=}6y1maH$=wyqr^eLXDU*wpLj2PP3wXPNe?PjV3 zd79FDSfY}pH$>3B^g}@e-AMGv3~kDa1RtI~Wi4DZqF*{$FclV;!MQi7y65Wr#(-N> zB%z0Hb8!B5Klj^hPE6(}I=Xr^@5u7G;)KBMNvAVkPG-0roVJ`DNFOvj@ctix~T#crvE-M+jyKU+Gocac>BKo<2GIy@FJU4cxbZjy#A76QkPe3nBKhpRWlG{AeuF- z>hC4S@+N#RBLnC{m)hdwkEo%c^m{+5JOay%!E2O#m1P1lA_6%~-}jZVS){W$%*rsT zd&g%0Z_Y;_r!{GI!KHVtIuz?)^jwGqs4;yW{ig2VN2a|H#|-GK)o4ys;ls(cs48*O z1<~`XlO)z=0HQuxx2*~E)rZMzoi6L)g9?Da(?0smcezP>iBkXNh-pRapKUcl_5bbk zKgx7ly%l?3D!~i6#(4Sj37w$#)?5`q=h>u^U&A=)gzCE9S4ech?+82q5p(}fbjHH` ztlVf(AfB=tjq&RI!ra{4tdgJ&favBnUwnw!W_`J>66k|IRc{x^K%wkB37`|X)60+8@58ZbvlB2~A z&|JzD27jvS>aw}qIbOukd7`@+<{rTw65D}MKTzuDTEXCmO4@T>{f(e(fh!b(KBL;} zxfY7}Ii@VLD4D>65+u}z2eFsNyUu;7O%!>%{&z3*a5)^}Y^GM7N-iy@L)I4sy8qJ-b z*Zd3|N(W)U77ImYt`eV@qEHt~%}?qw~>% z+i6i#Usri8vB_XCVsik@e+iA!hJpWRnW{>RP8kKOPMEnUk~p`FLK5Hz2R+ zta^8>(!@`ygsz2-CR{_f`6xlJX`fZ!b96V;qst5fRwR$F$#klyKP3fUrvVl*waDWG z3PQRu|0(j}i%IW;xtg3Fum-Cu-`#)p3j}k;qBPdmGO>^t@>VssdtSscY5&2nbu8vf zP?KxKNQRkL72_%(2WQ=~$6Wm*(9qO;I3@gx4=^PSbYvupKhOu`S7=i^&nBVgzdbuSvD*G0Yhvs{;P`om(j|dM0ovz@Tt*V&cCCv zJq^~v0$#hrHO}vdnT*9aYUM|t(I=0Hmtbefpn(Rm6Jl=CaD&yUZ90nSk}pl8MOnd~Q!$?b9jGy6-NaLXj`x zwftwU7b=uu*9y20sk-ree@Rcs@0P-o_4Th`yn6K?1dm6T3F~Tp&ykV+FT5D2fL+6& za5x-r1EIMgr^;1ZN^Cw$Si=0ua)1vaCT`5|>LIciKVNvej$c?~YyVc)&`lOw0JdtvVn{$KCH0Ja71oRC6$ot7!&*^5Ni0i)(JQp(a*Px19N*Fx)=U4h&ROmH{DS`?|S~lWLa2V1RN0DiX ze6_M9Bnt$hY1Fjf9&-x`rF9@Fq7a2#(GttkSuMa!a2oQYZo~dYJ)o^=N@~0?yK$zn zqXM}?yiz`W%(tkcE8TKzuBWT!QWyWa%OV{mSCaU|#i-O-#8O>gnUw39i) zdo&QK>v>N`fK&-?U&XTSuf*rFIWL0E3=6;hnGX0UOj+VV7jjKz^6(i%o>N)q@7*8i zCh2+q@BUxFqzVXxS!v=mxa`$ftnxg8Fq}xn<350B5!cl4!`xHiH>o5uGqdetQ}n{l z!gv;1P3!85RU&Ks()ZVffU{2onm+=3J~Yk@=X=dBdmTSCR~80&&w?=_d>7rrz*M!S zeVp_R+>A71&n2sma=iOsV(C2SVArvUy!)fB(S1Ce9Tj4cHG=DSxm3iWhMoj4ty129 zbV=b}chA9sL}8AhI^C8hgtk?~q(YU1|FGG&d*|2n_$!%p~|RLth#Dzn&LNZ~X6s2LlT#bl%fzz(b(G-)0P}pru_{y)A=2 zhd(}-nx8Ac*vBY-=ib3_dx-9vLp?4(RdFz1YH{5I3BaJyeg|HlR{1f0oktf%3hc3C zU_?dUyKR94O9e)ac5E=TOpwd`CIKuURu51uo3sQ>+x%*k-`Lq*+dUf!CjMiTftxB9 z7e1w<-YRMwpAd#Xmu-t<%{Mo`**>*kW`ZqEBOd+h<=u71oCD#R_VhaN*kc-W>Ch0p zT)KMp%K5FU)Gn{<{QLsZt&Rh9v*!J(?(WW*rbvbLB5!hEte%DW`jyt^De%?L>(bi3 z<9egA^icpTJeC_>AK|p!eHC@v-WxbST{d+EMUj26CJ4V{)*~e}h)wk^V-5ONduxwh zq=B(4*OcmdUi(|QH#a-vqsjcUQ&Z^Bsg76^T5Sgnt*w7>=+%I#Sx$3Gl1{^i`VQCA zltb9W{P{*_sq77|fbI&61&3?XSwy%*+{7eCZ!b?@QGSefkBN-5v^2098JV3FGKG#W z;96`ndwtNhYdzkvoK0!R#=*tiJFL>_@S07xUszbkVs)gT@Ouu|FE6R6s3_Z&y~toK z0xt-@=*p&PR7t;p7E73KH6ZA;=SUYvL+VXSPWbQdv^(2#a(Xn?>)H+wZ_f}x5{liB;`$z_@@(vY{(j61*k~2Nlq<^Kt zg%A<@Rp~Zfn>0{NZY=Icl-I)X?QnY0n=#3N(#a5(>Wqt(|f8x{Zs#_LT-|Ktle7>~LB+23ZvF@{OWiN}y&%I~v3wv%;GO{YA zyg~8z7q@dKTwBZ3xmZ@0RcU+%3b-!F;3_RK_`{X{% zto!b}`^UBmrQ;cP>Tf+Pqh0&)p9bp%#!t`ZM0Gu;f}q;W0i>>##(mIg(?_H6Ya#LK zjK5`n{X)mf!>Qkf=dt!oW07s^@nB9RBH~u&XM19e>Fv}hnAtc9!jNMUi}!&LvF7*K z>W(1hm9k>HdpQ*Z2T=R;YRH5<*zVTTx#Cq>{D;2Jl=kodDRL=GBeCxiI=``{37rgG zc$8(JLz=ZGJMmluFWc|NEmvnb*4gf0_w-dQLVeq;7nxdBI8lhxYD`&i(F*Mq2-%GS zkM-=2IpaffS4$&0nv8k-c@-zcx`WbjF{WjIPNr~~o*cz~#Y*FBViizrKb0&6EhTcdKIQ4;1@LgB2c*VI4?9^Z+S4c7(ZzXSfCNz4>}DD>;e|FVB32&?DwQf8X6R zE3_Xcn<76xf(!cWTT&a{PiMXWUhXcI%OoFx)(b&IuCt6y)J><⪻ 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�Jll&-I16`>JP^n|04YU`|XOJeBS5i6Gj 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 QV-8a1>FVdQ&MBb@01H?uy8r+H diff --git a/CharacterMap/CharacterMap/Services/ActivationService.cs b/CharacterMap/CharacterMap/Services/ActivationService.cs index 2b7119cb..e11a9bc4 100644 --- a/CharacterMap/CharacterMap/Services/ActivationService.cs +++ b/CharacterMap/CharacterMap/Services/ActivationService.cs @@ -83,6 +83,9 @@ void CreateMainView() _ = ApplicationViewSwitcher.TryShowAsStandaloneAsync(WindowService.MainWindow.View.Id); WindowService.MainWindow.CoreView.CoreWindow.Activate(); } + + // Screenshots + // ApplicationView.GetForCurrentView().TryResizeView(new Size(1594, 833)); } From 427709d63c305bd7194a7707f4b20e26b9195f3d Mon Sep 17 00:00:00 2001 From: Johnny Westlake Date: Tue, 7 Nov 2023 09:07:39 +0000 Subject: [PATCH 27/31] Simplify InfoBar --- CharacterMap/CharacterMap/CharacterMap.csproj | 1 + CharacterMap/CharacterMap/Controls/InfoBar.cs | 21 +++ CharacterMap/CharacterMap/Themes/Generic.xaml | 44 +++++ .../CharacterMap/Views/FontMapView.xaml | 177 ++++++------------ .../CharacterMap/Views/FontMapView.xaml.cs | 5 + 5 files changed, 124 insertions(+), 124 deletions(-) create mode 100644 CharacterMap/CharacterMap/Controls/InfoBar.cs diff --git a/CharacterMap/CharacterMap/CharacterMap.csproj b/CharacterMap/CharacterMap/CharacterMap.csproj index 3c0ca2a0..a7760a73 100644 --- a/CharacterMap/CharacterMap/CharacterMap.csproj +++ b/CharacterMap/CharacterMap/CharacterMap.csproj @@ -207,6 +207,7 @@ FilterFlyout.xaml + InstallFontDialog.xaml diff --git a/CharacterMap/CharacterMap/Controls/InfoBar.cs b/CharacterMap/CharacterMap/Controls/InfoBar.cs new file mode 100644 index 00000000..22706dce --- /dev/null +++ b/CharacterMap/CharacterMap/Controls/InfoBar.cs @@ -0,0 +1,21 @@ +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace CharacterMap.Controls; + +public sealed class InfoBar : ContentControl +{ + public object SecondaryContent + { + get { return (object)GetValue(SecondaryContentProperty); } + set { SetValue(SecondaryContentProperty, value); } + } + + public static readonly DependencyProperty SecondaryContentProperty = + DependencyProperty.Register(nameof(SecondaryContent), typeof(object), typeof(InfoBar), new PropertyMetadata(null)); + + public InfoBar() + { + this.DefaultStyleKey = typeof(InfoBar); + } +} diff --git a/CharacterMap/CharacterMap/Themes/Generic.xaml b/CharacterMap/CharacterMap/Themes/Generic.xaml index f245e3e3..f358aa7e 100644 --- a/CharacterMap/CharacterMap/Themes/Generic.xaml +++ b/CharacterMap/CharacterMap/Themes/Generic.xaml @@ -908,4 +908,48 @@ + + diff --git a/CharacterMap/CharacterMap/Views/FontMapView.xaml b/CharacterMap/CharacterMap/Views/FontMapView.xaml index 1a20d6a7..4df7ae42 100644 --- a/CharacterMap/CharacterMap/Views/FontMapView.xaml +++ b/CharacterMap/CharacterMap/Views/FontMapView.xaml @@ -1149,139 +1149,68 @@ + Spacing="0"> - - - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - - - - + + + + + + - - - - - + Content="{core:Localizer Key=HiddenGlyphsMessage/Text}"> + + + - + + - - - - + + diff --git a/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs b/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs index b7a84ec6..4a9a502f 100644 --- a/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs +++ b/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs @@ -1030,6 +1030,11 @@ private void VariableHintClick(object sender, RoutedEventArgs e) ViewToggleButton.Focus(FocusState.Keyboard); } + private void FilterHint_Click(object sender, RoutedEventArgs e) + { + CharacterFilterButton.Focus(FocusState.Keyboard); + } + From b5ac110bb07749073d8c047e35dc1c774498a1a2 Mon Sep 17 00:00:00 2001 From: Johnny Westlake Date: Fri, 10 Nov 2023 08:27:30 +0000 Subject: [PATCH 28/31] Code Cleanup --- CharacterMap/.editorconfig | 134 ++ .../Activation/ActivationHandler.cs | 39 +- .../DefaultLaunchActivationHandler.cs | 53 +- .../Activation/FileActivationHandler.cs | 7 +- CharacterMap/CharacterMap/App.xaml.cs | 8 +- CharacterMap/CharacterMap/CharacterMap.csproj | 4 +- .../CharacterMap/Controls/AutoGrid.cs | 39 +- .../CharacterMap/Controls/ButtonGroup.cs | 379 ++-- .../CharacterMap/Controls/CategoryFlyout.cs | 172 +- .../Controls/CharacterGridView.cs | 737 +++--- .../CharacterMap/Controls/CharacterPicker.cs | 122 +- .../Controls/CharacterPickerButton.cs | 162 +- .../CharacterMap/Controls/ContentGroup.cs | 1 - .../Controls/CreateCollectionDialog.xaml.cs | 164 +- .../CharacterMap/Controls/DirectTextBlock.cs | 358 ++- .../CharacterMap/Controls/ExtendedListView.cs | 318 ++- .../Controls/ExtendedSplitView.cs | 87 +- .../Controls/FilterFlyout.xaml.cs | 365 ++- .../Controls/FlowAwareComboBox.cs | 158 +- .../Controls/FontMapPrintPage.xaml.cs | 236 +- .../CharacterMap/Controls/LabelButton.cs | 129 +- .../Controls/MenuFlyoutContentHost.cs | 35 +- .../Controls/ModalPagePresenter.cs | 265 ++- .../Controls/OpenFolderDialog.xaml.cs | 237 +- .../Controls/SettingsPresenter.cs | 330 ++- .../CharacterMap/Controls/SuggestionBox.cs | 451 ++-- .../Controls/Toolkit/AdaptiveGridView.cs | 4 +- .../Controls/Toolkit/GridSplitter.cs | 1987 ++++++++--------- .../Controls/Toolkit/InAppNotification.cs | 1079 +++++---- .../Controls/Toolkit/WrapPanel.cs | 559 +++-- .../CharacterMap/Controls/UXButton.cs | 207 +- .../CharacterMap/Controls/UXComboBox.cs | 206 +- .../CharacterMap/Controls/UXRadioButton.cs | 85 +- .../CharacterMap/Controls/UXSlider.cs | 69 +- .../Controls/UnhandledExceptionDialog.xaml.cs | 71 +- .../CharacterMap/Controls/XamlTitleBar.cs | 564 +++-- .../CharacterMap/Converters/Converters.cs | 8 +- .../CharacterMap/Core/AlphaKeyGroup.cs | 3 +- CharacterMap/CharacterMap/Core/AppSettings.cs | 677 +++--- CharacterMap/CharacterMap/Core/Converters.cs | 249 +-- ...ExceptionHandlingSynchronizationContext.cs | 4 +- .../CharacterMap/Core/ExportManager.Font.cs | 298 ++- .../CharacterMap/Core/ExportManager.cs | 973 ++++---- CharacterMap/CharacterMap/Core/FontFinder.cs | 72 +- CharacterMap/CharacterMap/Core/FontVariant.cs | 22 +- .../CharacterMap/Core/InstalledFont.cs | 4 +- CharacterMap/CharacterMap/Core/Localizer.cs | 12 +- .../CharacterMap/Core/PanoseParser.cs | 263 ++- CharacterMap/CharacterMap/Core/Properties.cs | 26 +- .../CharacterMap/Core/SVGPathReciever.cs | 103 +- .../CharacterMap/Core/TypographyAnalyzer.cs | 8 +- CharacterMap/CharacterMap/Core/Utils.cs | 14 +- .../CharacterMap/GlobalSuppressions.cs | 6 +- .../CharacterMap/Helpers/Animation.cs | 1222 +++++----- .../CharacterMap/Helpers/Composition.cs | 1458 ++++++------ .../Helpers/CompositionExtensions.cs | 184 +- .../Helpers/CompositionFactory.cs | 1243 +++++------ .../Helpers/CompositionNaturalMotion.cs | 205 +- .../Helpers/CompositionProperty.cs | 123 +- .../CharacterMap/Helpers/Extensions.cs | 20 +- .../CharacterMap/Helpers/FluentAnimation.cs | 725 +++--- .../CharacterMap/Helpers/FlyoutHelper.cs | 40 +- .../CharacterMap/Helpers/FontConverter.cs | 10 +- .../CharacterMap/Helpers/PrintHelper.cs | 7 +- .../CharacterMap/Helpers/ResourceHelper.cs | 2 +- CharacterMap/CharacterMap/Helpers/Sqlite.cs | 455 ++-- .../CharacterMap/Helpers/TitleBarHelper.cs | 87 +- CharacterMap/CharacterMap/Helpers/Unicode.cs | 5 +- .../Helpers/VisualTreeHelperExtensions.cs | 595 +++-- .../CharacterMap/Helpers/WoffToOtf.cs | 6 +- .../Models/AdobeGlyphListMapping.cs | 30 +- .../CharacterMap/Models/BasicFontFilter.cs | 30 +- .../Models/CharacterRenderingOptions.cs | 10 +- CharacterMap/CharacterMap/Models/DevOption.cs | 4 +- CharacterMap/CharacterMap/Models/FontItem.cs | 4 +- CharacterMap/CharacterMap/Models/Messages.cs | 2 +- .../CharacterMap/Models/SearchResultsGroup.cs | 2 +- .../CharacterMap/Models/SearchTarget.cs | 2 +- .../CharacterMap/Models/SupportedLanguage.cs | 4 +- .../CharacterMap/Models/UnicodeGlyphData.cs | 2 +- .../CharacterMap/Models/UnicodeRange.cs | 4 +- .../CharacterMap/Models/UnicodeRangeGroup.cs | 6 +- .../CharacterMap/Models/UnicodeRangeModel.cs | 2 +- .../CharacterMap/Models/UnicodeScriptTags.cs | 14 +- CharacterMap/CharacterMap/Models/Unihan.cs | 4 +- .../CharacterMap/Properties/Annotations.cs | 1767 ++++++++------- .../CharacterMap/Properties/AssemblyInfo.cs | 1 - .../Provider/CSharpDevProvider.cs | 99 +- .../CharacterMap/Provider/CppCxDevProvider.cs | 84 +- .../Provider/CppWinrtDevProvider.cs | 127 +- .../CharacterMap/Provider/DevProviderBase.cs | 255 +-- .../CharacterMap/Provider/DevProviderNone.cs | 23 +- .../CharacterMap/Provider/DevProviderType.cs | 27 +- .../Provider/GeometryCacheEntry.cs | 71 +- .../Provider/SQLiteCollectionProvider.cs | 199 +- .../Provider/SQLiteGlyphProvider.Debug.cs | 855 ++++--- .../Provider/SQLiteGlyphProvider.cs | 493 ++-- .../Provider/UnicodeDevProvider.cs | 91 +- .../CharacterMap/Provider/VBDevProvider.cs | 65 +- .../Provider/XamarinFormsDevProvider.cs | 62 +- .../CharacterMap/Provider/XamlDevProvider.cs | 9 +- .../Services/ActivationService.cs | 210 +- .../CharacterMap/Services/GlyphService.cs | 347 ++- .../CharacterMap/Services/IDialogService.cs | 15 +- .../Services/LeakTrackingService.cs | 169 +- .../Services/NavigationServiceEx.cs | 90 +- .../Services/ToastNotificationsService.cs | 22 +- .../Services/UserCollectionsService.cs | 464 ++-- .../Services/UserFontCollection.cs | 50 +- .../Services/ViewLifetimeManager.cs | 362 ++- .../CharacterMap/Services/WindowService.cs | 400 ++-- CharacterMap/CharacterMap/Styles/Button.xaml | 15 +- .../CharacterMap/Styles/Controls.xaml.cs | 75 +- .../CharacterMap/Styles/ItemTemplates.xaml.cs | 207 +- .../CharacterMap/Themes/DefaultTheme.xaml.cs | 57 +- .../ViewModels/CalligraphyViewModel.cs | 631 +++--- .../CollectionManagementViewModel.cs | 254 +-- .../ViewModels/ExportViewModel.cs | 299 ++- .../ViewModels/FontMapViewModel.cs | 1198 +++++----- .../CharacterMap/ViewModels/MainViewModel.cs | 973 ++++---- .../CharacterMap/ViewModels/PrintViewModel.cs | 269 ++- .../ViewModels/QuickCompareViewModel.cs | 310 ++- .../ViewModels/SettingsViewModel.cs | 522 ++--- .../CharacterMap/ViewModels/ViewModelBase.cs | 377 ++-- .../ViewModels/ViewModelLocator.cs | 77 +- .../Views/CalligraphyView.xaml.cs | 566 +++-- .../Views/CollectionManagementView.xaml.cs | 152 +- .../CharacterMap/Views/ExportView.xaml.cs | 140 +- .../Views/FontMapView.Animation.cs | 687 +++--- .../CharacterMap/Views/FontMapView.xaml.cs | 1874 ++++++++-------- .../CharacterMap/Views/MainPage.xaml.cs | 1490 ++++++------ .../CharacterMap/Views/PopoverViewBase.cs | 73 +- .../CharacterMap/Views/PrintView.xaml.cs | 413 ++-- .../Views/QuickCompareView.xaml.cs | 746 +++---- .../CharacterMap/Views/SettingsView.xaml.cs | 549 +++-- .../CharacterMap/Views/TestView.xaml.cs | 32 +- CharacterMap/CharacterMap/Views/ViewBase.cs | 165 +- 137 files changed, 18571 insertions(+), 19007 deletions(-) diff --git a/CharacterMap/.editorconfig b/CharacterMap/.editorconfig index 76bac021..f2ac64ee 100644 --- a/CharacterMap/.editorconfig +++ b/CharacterMap/.editorconfig @@ -14,3 +14,137 @@ dotnet_diagnostic.IDE0058.severity = none # Default severity for analyzer diagnostics with category 'Style' dotnet_analyzer_diagnostic.category-Style.severity = none + +[*.cs] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.private_prop_should_be_begins_with_underscore.severity = suggestion +dotnet_naming_rule.private_prop_should_be_begins_with_underscore.symbols = private_prop +dotnet_naming_rule.private_prop_should_be_begins_with_underscore.style = begins_with_underscore + +# Symbol specifications + +dotnet_naming_symbols.private_prop.applicable_kinds = property, field +dotnet_naming_symbols.private_prop.applicable_accessibilities = private +dotnet_naming_symbols.private_prop.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_underscore.required_prefix = _ +dotnet_naming_style.begins_with_underscore.required_suffix = +dotnet_naming_style.begins_with_underscore.word_separator = +dotnet_naming_style.begins_with_underscore.capitalization = camel_case +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = file_scoped:suggestion +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_indent_labels = one_less_than_current +csharp_space_around_binary_operators = before_and_after +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_prefer_static_local_function = true:suggestion +csharp_style_prefer_readonly_struct = true:suggestion +csharp_style_prefer_readonly_struct_member = true:suggestion +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent +csharp_style_conditional_delegate_call = true:suggestion + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_namespace_match_folder = true:suggestion +dotnet_style_readonly_field = true:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_allow_multiple_blank_lines_experimental = true:silent +dotnet_style_allow_statement_immediately_after_block_experimental = true:silent diff --git a/CharacterMap/CharacterMap/Activation/ActivationHandler.cs b/CharacterMap/CharacterMap/Activation/ActivationHandler.cs index e5ef4a1d..2f1255dc 100644 --- a/CharacterMap/CharacterMap/Activation/ActivationHandler.cs +++ b/CharacterMap/CharacterMap/Activation/ActivationHandler.cs @@ -1,30 +1,27 @@ -using System.Threading.Tasks; +namespace CharacterMap.Activation; -namespace CharacterMap.Activation +public abstract class ActivationHandler { - public abstract class ActivationHandler + public abstract bool CanHandle(object args); + public abstract Task HandleAsync(object args); +} + +public abstract class ActivationHandler : ActivationHandler where T : class +{ + protected abstract Task HandleInternalAsync(T args); + + public override async Task HandleAsync(object args) { - public abstract bool CanHandle(object args); - public abstract Task HandleAsync(object args); + await HandleInternalAsync(args as T); } - public abstract class ActivationHandler : ActivationHandler where T : class + public override bool CanHandle(object args) { - protected abstract Task HandleInternalAsync(T args); - - public override async Task HandleAsync(object args) - { - await HandleInternalAsync(args as T); - } - - public override bool CanHandle(object args) - { - return args is T && CanHandleInternal(args as T); - } + return args is T && CanHandleInternal(args as T); + } - protected virtual bool CanHandleInternal(T args) - { - return true; - } + protected virtual bool CanHandleInternal(T args) + { + return true; } } diff --git a/CharacterMap/CharacterMap/Activation/DefaultLaunchActivationHandler.cs b/CharacterMap/CharacterMap/Activation/DefaultLaunchActivationHandler.cs index fb4608b6..708bd605 100644 --- a/CharacterMap/CharacterMap/Activation/DefaultLaunchActivationHandler.cs +++ b/CharacterMap/CharacterMap/Activation/DefaultLaunchActivationHandler.cs @@ -1,38 +1,33 @@ -using System; -using System.Threading.Tasks; -using CharacterMap.Services; -using CommunityToolkit.Mvvm.DependencyInjection; using Windows.ApplicationModel.Activation; -namespace CharacterMap.Activation +namespace CharacterMap.Activation; + +internal class DefaultLaunchActivationHandler : ActivationHandler { - internal class DefaultLaunchActivationHandler : ActivationHandler + private readonly string _navElement; + + private NavigationServiceEx NavigationService => Ioc.Default.GetService(); + + public DefaultLaunchActivationHandler(Type navElement) { - private readonly string _navElement; - - private NavigationServiceEx NavigationService => Ioc.Default.GetService(); + _navElement = navElement.FullName; + } - public DefaultLaunchActivationHandler(Type navElement) - { - _navElement = navElement.FullName; - } - - protected override async Task HandleInternalAsync(ILaunchActivatedEventArgs args) - { - // When the navigation stack isn't restored navigate to the first page, - // configuring the new page by passing required information as a navigation - // parameter - NavigationService.Navigate(_navElement, args.Arguments); + protected override async Task HandleInternalAsync(ILaunchActivatedEventArgs args) + { + // When the navigation stack isn't restored navigate to the first page, + // configuring the new page by passing required information as a navigation + // parameter + NavigationService.Navigate(_navElement, args.Arguments); - // You can use this sample to create toast notifications where needed in your app. - // Singleton.Instance.ShowSavedNotification(); - await Task.CompletedTask; - } + // You can use this sample to create toast notifications where needed in your app. + // Singleton.Instance.ShowSavedNotification(); + await Task.CompletedTask; + } - protected override bool CanHandleInternal(ILaunchActivatedEventArgs args) - { - // None of the ActivationHandlers has handled the app activation - return NavigationService.Frame.Content == null; - } + protected override bool CanHandleInternal(ILaunchActivatedEventArgs args) + { + // None of the ActivationHandlers has handled the app activation + return NavigationService.Frame.Content == null; } } diff --git a/CharacterMap/CharacterMap/Activation/FileActivationHandler.cs b/CharacterMap/CharacterMap/Activation/FileActivationHandler.cs index e3762009..3f9f38af 100644 --- a/CharacterMap/CharacterMap/Activation/FileActivationHandler.cs +++ b/CharacterMap/CharacterMap/Activation/FileActivationHandler.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Windows.ApplicationModel.Activation; +using Windows.ApplicationModel.Activation; namespace CharacterMap.Activation { diff --git a/CharacterMap/CharacterMap/App.xaml.cs b/CharacterMap/CharacterMap/App.xaml.cs index 6ce95093..ab44ae0a 100644 --- a/CharacterMap/CharacterMap/App.xaml.cs +++ b/CharacterMap/CharacterMap/App.xaml.cs @@ -1,10 +1,10 @@ -using Windows.ApplicationModel.Activation; -using Windows.UI.Xaml; using CharacterMap.Controls; -using UnhandledExceptionEventArgs = CharacterMap.Core.UnhandledExceptionEventArgs; using CharacterMapCX.Controls; -using Windows.ApplicationModel.Core; using SQLite; +using Windows.ApplicationModel.Activation; +using Windows.ApplicationModel.Core; +using Windows.UI.Xaml; +using UnhandledExceptionEventArgs = CharacterMap.Core.UnhandledExceptionEventArgs; namespace CharacterMap; diff --git a/CharacterMap/CharacterMap/CharacterMap.csproj b/CharacterMap/CharacterMap/CharacterMap.csproj index a7760a73..cea1e4b5 100644 --- a/CharacterMap/CharacterMap/CharacterMap.csproj +++ b/CharacterMap/CharacterMap/CharacterMap.csproj @@ -144,10 +144,10 @@ --> - 8.2.0 + 8.2.2 - 6.0.1 + 7.0.0 6.2.14 diff --git a/CharacterMap/CharacterMap/Controls/AutoGrid.cs b/CharacterMap/CharacterMap/Controls/AutoGrid.cs index d59fbbe6..e157c2f8 100644 --- a/CharacterMap/CharacterMap/Controls/AutoGrid.cs +++ b/CharacterMap/CharacterMap/Controls/AutoGrid.cs @@ -1,30 +1,27 @@ -using System.Linq; -using Windows.Foundation; -using Windows.UI.Xaml; +using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace CharacterMap.Controls +namespace CharacterMap.Controls; + +public class AutoGrid : Grid { - public class AutoGrid : Grid + protected override Size ArrangeOverride(Size finalSize) { - protected override Size ArrangeOverride(Size finalSize) - { - var rows = this.Children.OfType().Count(c => Grid.GetColumn(c) == 0); - while (this.RowDefinitions.Count > rows) - this.RowDefinitions.RemoveAt(0); - while (this.RowDefinitions.Count < rows) - this.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); - - int row = -1; - foreach (var child in this.Children.OfType()) - { - if (Grid.GetColumn(child) == 0) - row++; + var rows = this.Children.OfType().Count(c => Grid.GetColumn(c) == 0); + while (this.RowDefinitions.Count > rows) + this.RowDefinitions.RemoveAt(0); + while (this.RowDefinitions.Count < rows) + this.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); - Grid.SetRow(child, row); - } + int row = -1; + foreach (var child in this.Children.OfType()) + { + if (Grid.GetColumn(child) == 0) + row++; - return base.ArrangeOverride(finalSize); + Grid.SetRow(child, row); } + + return base.ArrangeOverride(finalSize); } } diff --git a/CharacterMap/CharacterMap/Controls/ButtonGroup.cs b/CharacterMap/CharacterMap/Controls/ButtonGroup.cs index 2525ec78..6dd087d3 100644 --- a/CharacterMap/CharacterMap/Controls/ButtonGroup.cs +++ b/CharacterMap/CharacterMap/Controls/ButtonGroup.cs @@ -1,248 +1,245 @@ -using System; -using Windows.Foundation; -using Windows.UI.Xaml; +using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Data; -namespace CharacterMap.Controls +namespace CharacterMap.Controls; + +public class ButtonLabel : DependencyObject { - public class ButtonLabel : DependencyObject + public string Shortcut { - public string Shortcut - { - get { return (string)GetValue(ShortcutProperty); } - set { SetValue(ShortcutProperty, value); } - } - - public static readonly DependencyProperty ShortcutProperty = - DependencyProperty.Register(nameof(Shortcut), typeof(string), typeof(ButtonLabel), new PropertyMetadata(null)); + get { return (string)GetValue(ShortcutProperty); } + set { SetValue(ShortcutProperty, value); } + } - public string Glyph - { - get { return (string)GetValue(GlyphProperty); } - set { SetValue(GlyphProperty, value); } - } + public static readonly DependencyProperty ShortcutProperty = + DependencyProperty.Register(nameof(Shortcut), typeof(string), typeof(ButtonLabel), new PropertyMetadata(null)); - public static readonly DependencyProperty GlyphProperty = - DependencyProperty.Register(nameof(Glyph), typeof(string), typeof(ButtonLabel), new PropertyMetadata(null)); + public string Glyph + { + get { return (string)GetValue(GlyphProperty); } + set { SetValue(GlyphProperty, value); } + } - public string Title - { - get { return (string)GetValue(TitleProperty); } - set { SetValue(TitleProperty, value); } - } + public static readonly DependencyProperty GlyphProperty = + DependencyProperty.Register(nameof(Glyph), typeof(string), typeof(ButtonLabel), new PropertyMetadata(null)); - public static readonly DependencyProperty TitleProperty = - DependencyProperty.Register(nameof(Title), typeof(string), typeof(ButtonLabel), new PropertyMetadata(null)); + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } - public string Description - { - get { return (string)GetValue(DescriptionProperty); } - set { SetValue(DescriptionProperty, value); } - } + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register(nameof(Title), typeof(string), typeof(ButtonLabel), new PropertyMetadata(null)); - public static readonly DependencyProperty DescriptionProperty = - DependencyProperty.Register(nameof(Description), typeof(string), typeof(ButtonLabel), new PropertyMetadata(null)); + public string Description + { + get { return (string)GetValue(DescriptionProperty); } + set { SetValue(DescriptionProperty, value); } } - class ButtonGroupBorderConverter : IValueConverter + public static readonly DependencyProperty DescriptionProperty = + DependencyProperty.Register(nameof(Description), typeof(string), typeof(ButtonLabel), new PropertyMetadata(null)); +} + +class ButtonGroupBorderConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, string language) { - public object Convert(object value, Type targetType, object parameter, string language) - { - if (value is Thickness t && parameter is string s) - return s switch - { - "Left" => new Thickness(t.Left, t.Top, t.Right / 2d, t.Bottom), - "Right" => new Thickness(t.Left / 2d, t.Top, t.Right, t.Bottom), - "Top" => new Thickness(t.Left, t.Top, t.Right, t.Bottom / 2d), - "Bottom" => new Thickness(t.Left, t.Top /2d, t.Right, t.Bottom), - "Vert" => new Thickness(t.Left, t.Top /2d, t.Right, t.Bottom / 2d), - _ => new Thickness(t.Left / 2d, t.Top, t.Right / 2d, t.Bottom) - }; - - return value; - } + if (value is Thickness t && parameter is string s) + return s switch + { + "Left" => new Thickness(t.Left, t.Top, t.Right / 2d, t.Bottom), + "Right" => new Thickness(t.Left / 2d, t.Top, t.Right, t.Bottom), + "Top" => new Thickness(t.Left, t.Top, t.Right, t.Bottom / 2d), + "Bottom" => new Thickness(t.Left, t.Top / 2d, t.Right, t.Bottom), + "Vert" => new Thickness(t.Left, t.Top / 2d, t.Right, t.Bottom / 2d), + _ => new Thickness(t.Left / 2d, t.Top, t.Right / 2d, t.Bottom) + }; + + return value; + } - public object ConvertBack(object value, Type targetType, object parameter, string language) - { - throw new NotImplementedException(); - } + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); } +} - class ButtonGroupCornerConverter : IValueConverter +class ButtonGroupCornerConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, string language) { - public object Convert(object value, Type targetType, object parameter, string language) - { - if (value is CornerRadius r && parameter is string s) - return s switch - { - "Left" => new CornerRadius(r.TopLeft, 0, 0, r.BottomLeft), - "Right" => new CornerRadius(0, r.TopRight, r.BottomRight, 0), - "Top" => new CornerRadius(r.TopLeft, r.TopRight, 0, 0), - "Bottom" => new CornerRadius(0, 0, r.BottomRight, r.BottomLeft), - _ => new CornerRadius(0), - }; - - return value; - } + if (value is CornerRadius r && parameter is string s) + return s switch + { + "Left" => new CornerRadius(r.TopLeft, 0, 0, r.BottomLeft), + "Right" => new CornerRadius(0, r.TopRight, r.BottomRight, 0), + "Top" => new CornerRadius(r.TopLeft, r.TopRight, 0, 0), + "Bottom" => new CornerRadius(0, 0, r.BottomRight, r.BottomLeft), + _ => new CornerRadius(0), + }; + + return value; + } - public object ConvertBack(object value, Type targetType, object parameter, string language) - { - throw new NotImplementedException(); - } + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); } +} - public sealed class ButtonGroup : ItemsControl +public sealed class ButtonGroup : ItemsControl +{ + public Orientation Orientation { - public Orientation Orientation - { - get { return (Orientation)GetValue(OrientationProperty); } - set { SetValue(OrientationProperty, value); } - } + get { return (Orientation)GetValue(OrientationProperty); } + set { SetValue(OrientationProperty, value); } + } - public static readonly DependencyProperty OrientationProperty = - DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(ButtonGroup), new PropertyMetadata(Orientation.Horizontal)); + public static readonly DependencyProperty OrientationProperty = + DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(ButtonGroup), new PropertyMetadata(Orientation.Horizontal)); - private static ButtonGroupBorderConverter _bConv { get; } = new(); - private static ButtonGroupCornerConverter _cConv { get; } = new(); + private static ButtonGroupBorderConverter _bConv { get; } = new(); + private static ButtonGroupCornerConverter _cConv { get; } = new(); - private long _bToken = 0; - private long _cToken = 0; + private long _bToken = 0; + private long _cToken = 0; - public ButtonGroup() - { - this.DefaultStyleKey = typeof(ButtonGroup); + public ButtonGroup() + { + this.DefaultStyleKey = typeof(ButtonGroup); - this.Loaded += ButtonGroup_Loaded; - this.Unloaded += ButtonGroup_Unloaded; - } + this.Loaded += ButtonGroup_Loaded; + this.Unloaded += ButtonGroup_Unloaded; + } - private void ButtonGroup_Loaded(object sender, RoutedEventArgs e) - { - UpdateBindings(); + private void ButtonGroup_Loaded(object sender, RoutedEventArgs e) + { + UpdateBindings(); - _cToken = this.RegisterPropertyChangedCallback(ItemsControl.CornerRadiusProperty, OnDPChanged); - _bToken = this.RegisterPropertyChangedCallback(ItemsControl.BorderThicknessProperty, OnDPChanged); - } + _cToken = this.RegisterPropertyChangedCallback(ItemsControl.CornerRadiusProperty, OnDPChanged); + _bToken = this.RegisterPropertyChangedCallback(ItemsControl.BorderThicknessProperty, OnDPChanged); + } - private void ButtonGroup_Unloaded(object sender, RoutedEventArgs e) - { - this.UnregisterPropertyChangedCallback(ItemsControl.CornerRadiusProperty, _cToken); - this.UnregisterPropertyChangedCallback(ItemsControl.BorderThicknessProperty, _bToken); - } + private void ButtonGroup_Unloaded(object sender, RoutedEventArgs e) + { + this.UnregisterPropertyChangedCallback(ItemsControl.CornerRadiusProperty, _cToken); + this.UnregisterPropertyChangedCallback(ItemsControl.BorderThicknessProperty, _bToken); + } - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - UpdateBindings(); - } + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + UpdateBindings(); + } - private void OnDPChanged(DependencyObject sender, DependencyProperty dp) - { - UpdateBindings(); - } + private void OnDPChanged(DependencyObject sender, DependencyProperty dp) + { + UpdateBindings(); + } - protected override void OnItemsChanged(object e) - { - UpdateBindings(); - } + protected override void OnItemsChanged(object e) + { + UpdateBindings(); + } - protected override Size MeasureOverride(Size availableSize) - { - var measure = base.MeasureOverride(availableSize); + protected override Size MeasureOverride(Size availableSize) + { + var measure = base.MeasureOverride(availableSize); - if (ItemsPanelRoot is Grid g) + if (ItemsPanelRoot is Grid g) + { + if (Orientation == Orientation.Horizontal) { - if (Orientation == Orientation.Horizontal) - { - g.RowDefinitions.Clear(); - - if (g.ColumnDefinitions.Count != Items.Count) - { - while (g.ColumnDefinitions.Count > Items.Count) - g.ColumnDefinitions.RemoveAt(0); + g.RowDefinitions.Clear(); - while (g.ColumnDefinitions.Count < Items.Count) - g.ColumnDefinitions.Add(new()); - } + if (g.ColumnDefinitions.Count != Items.Count) + { + while (g.ColumnDefinitions.Count > Items.Count) + g.ColumnDefinitions.RemoveAt(0); - for (int i = 0; i < Items.Count; i++) - if (Items[i] is FrameworkElement f) - Grid.SetColumn(f, i); + while (g.ColumnDefinitions.Count < Items.Count) + g.ColumnDefinitions.Add(new()); } - else - { - g.ColumnDefinitions.Clear(); - if (g.RowDefinitions.Count != Items.Count) - { - while (g.RowDefinitions.Count > Items.Count) - g.RowDefinitions.RemoveAt(0); + for (int i = 0; i < Items.Count; i++) + if (Items[i] is FrameworkElement f) + Grid.SetColumn(f, i); + } + else + { + g.ColumnDefinitions.Clear(); - while (g.RowDefinitions.Count < Items.Count) - g.RowDefinitions.Add(new()); - } + if (g.RowDefinitions.Count != Items.Count) + { + while (g.RowDefinitions.Count > Items.Count) + g.RowDefinitions.RemoveAt(0); - for (int i = 0; i < Items.Count; i++) - if (Items[i] is FrameworkElement f) - Grid.SetRow(f, i); + while (g.RowDefinitions.Count < Items.Count) + g.RowDefinitions.Add(new()); } - + + for (int i = 0; i < Items.Count; i++) + if (Items[i] is FrameworkElement f) + Grid.SetRow(f, i); } - return measure; } - void UpdateBindings() + return measure; + } + + void UpdateBindings() + { + void Bind(Control c, string param = null) { - void Bind(Control c, string param = null) + if (c == null) + return; + + c.SetBinding(Control.BorderBrushProperty, new Binding { - if (c == null) - return; + Source = this, + Path = new(nameof(BorderBrush)) + }); - c.SetBinding(Control.BorderBrushProperty, new Binding - { - Source = this, - Path = new(nameof(BorderBrush)) - }); + c.SetBinding(Control.BorderThicknessProperty, new Binding + { + Source = this, + Path = new(nameof(BorderThickness)), + ConverterParameter = param, + Converter = param is not null ? _bConv : null + }); - c.SetBinding(Control.BorderThicknessProperty, new Binding - { - Source = this, - Path = new(nameof(BorderThickness)), - ConverterParameter = param, - Converter = param is not null ? _bConv : null - }); + c.SetBinding(Control.CornerRadiusProperty, new Binding + { + Source = this, + Path = new(nameof(CornerRadius)), + ConverterParameter = param, + Converter = param is not null ? _cConv : null + }); + } - c.SetBinding(Control.CornerRadiusProperty, new Binding - { - Source = this, - Path = new(nameof(CornerRadius)), - ConverterParameter = param, - Converter = param is not null ? _cConv : null - }); - } + bool h = Orientation == Orientation.Horizontal; - bool h = Orientation == Orientation.Horizontal; + // 1. Set bindings for children + if (Items.Count == 1 && Items[0] is Control c) + Bind(c); + else if (Items.Count > 1) + { + // Set First element + if (Items[0] is Control c1) + Bind(c1, h ? "Left" : "Top"); - // 1. Set bindings for children - if (Items.Count == 1 && Items[0] is Control c) - Bind(c); - else if (Items.Count > 1) - { - // Set First element - if (Items[0] is Control c1) - Bind(c1, h ? "Left" : "Top"); - - // Handle last element - if (Items[^1] is Control c2) - Bind(c2, h ? "Right" : "Bottom"); - - // Handle everything in the middle - if (Items.Count > 2) - for (int i = 1; i < Items.Count - 1; i++) - Bind(Items[i] as Control, h ? "Mid" : "Vert"); - } + // Handle last element + if (Items[^1] is Control c2) + Bind(c2, h ? "Right" : "Bottom"); + + // Handle everything in the middle + if (Items.Count > 2) + for (int i = 1; i < Items.Count - 1; i++) + Bind(Items[i] as Control, h ? "Mid" : "Vert"); } } } diff --git a/CharacterMap/CharacterMap/Controls/CategoryFlyout.cs b/CharacterMap/CharacterMap/Controls/CategoryFlyout.cs index 3a37f5cd..714e4a5c 100644 --- a/CharacterMap/CharacterMap/Controls/CategoryFlyout.cs +++ b/CharacterMap/CharacterMap/Controls/CategoryFlyout.cs @@ -1,119 +1,113 @@ -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.ViewModels; -using System; -using System.Collections.Generic; -using Windows.UI.Xaml; +using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace CharacterMap.Controls +namespace CharacterMap.Controls; + +public sealed class CategoryFlyout : Control { - public sealed class CategoryFlyout : Control + public event EventHandler> AcceptClicked; + + public object SourceCategories { - public event EventHandler> AcceptClicked; + get { return (object)GetValue(SourceCategoriesProperty); } + set { SetValue(SourceCategoriesProperty, value); } + } - public object SourceCategories - { - get { return (object)GetValue(SourceCategoriesProperty); } - set { SetValue(SourceCategoriesProperty, value); } - } + public static readonly DependencyProperty SourceCategoriesProperty = + DependencyProperty.Register(nameof(SourceCategories), typeof(object), typeof(CategoryFlyout), new PropertyMetadata(null)); - public static readonly DependencyProperty SourceCategoriesProperty = - DependencyProperty.Register(nameof(SourceCategories), typeof(object), typeof(CategoryFlyout), new PropertyMetadata(null)); + public Flyout Flyout + { + get { return (Flyout)GetValue(FlyoutProperty); } + set { SetValue(FlyoutProperty, value); } + } - public Flyout Flyout - { - get { return (Flyout)GetValue(FlyoutProperty); } - set { SetValue(FlyoutProperty, value); } - } + public static readonly DependencyProperty FlyoutProperty = + DependencyProperty.Register(nameof(Flyout), typeof(Flyout), typeof(CategoryFlyout), new PropertyMetadata(null)); - public static readonly DependencyProperty FlyoutProperty = - DependencyProperty.Register(nameof(Flyout), typeof(Flyout), typeof(CategoryFlyout), new PropertyMetadata(null)); + private Button _appBtnSelectAll = null; + private Button _appBtnClear = null; + private Button _appBtnReset = null; + private Button _btnApply = null; + private ListViewBase _categoryList = null; - private Button _appBtnSelectAll = null; - private Button _appBtnClear = null; - private Button _appBtnReset = null; - private Button _btnApply = null; - private ListViewBase _categoryList = null; + private object _listSrc = null; - private object _listSrc = null; + public CategoryFlyout() + { + this.DefaultStyleKey = typeof(CategoryFlyout); + } - public CategoryFlyout() - { - this.DefaultStyleKey = typeof(CategoryFlyout); - } + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); - protected override void OnApplyTemplate() + _categoryList ??= this.GetTemplateChild("CategoryList") as ListViewBase; + + if (_appBtnSelectAll is null && this.GetTemplateChild("AppBtnSelectAll") is Button sa) { - base.OnApplyTemplate(); - - _categoryList ??= this.GetTemplateChild("CategoryList") as ListViewBase; - - if (_appBtnSelectAll is null && this.GetTemplateChild("AppBtnSelectAll") is Button sa) - { - _appBtnSelectAll = sa; - sa.Click -= FilterSelectAll_Click; - sa.Click += FilterSelectAll_Click; - } - - if (_appBtnClear is null && this.GetTemplateChild("AppBtnClear") is Button cb) - { - _appBtnClear = cb; - cb.Click -= FilterClear_Click; - cb.Click += FilterClear_Click; - } - - if (_appBtnReset is null && this.GetTemplateChild("AppBtnReset") is Button br) - { - _appBtnReset = br; - br.Click -= FilterRefresh_Click; - br.Click += FilterRefresh_Click; - } - - if (_btnApply is null && this.GetTemplateChild("BtnApply") is Button ba) - { - _btnApply = ba; - ba.Click -= FilterAccept_Click; - ba.Click += FilterAccept_Click; - } + _appBtnSelectAll = sa; + sa.Click -= FilterSelectAll_Click; + sa.Click += FilterSelectAll_Click; } - public void OnOpening() + if (_appBtnClear is null && this.GetTemplateChild("AppBtnClear") is Button cb) { - if (_categoryList.ItemsSource is IReadOnlyList list - && SourceCategories == _listSrc) - { - //foreach (var item in list) - // item.IsSelected = true; - } - else - { - _listSrc = SourceCategories; - _categoryList.ItemsSource = Unicode.CreateRangesList(SourceCategories as List); - } + _appBtnClear = cb; + cb.Click -= FilterClear_Click; + cb.Click += FilterClear_Click; } - private void FilterAccept_Click(object sender, RoutedEventArgs e) + if (_appBtnReset is null && this.GetTemplateChild("AppBtnReset") is Button br) { - AcceptClicked?.Invoke(this, (List)_categoryList.ItemsSource); - Flyout?.Hide(); + _appBtnReset = br; + br.Click -= FilterRefresh_Click; + br.Click += FilterRefresh_Click; } - private void FilterRefresh_Click(object sender, RoutedEventArgs e) + if (_btnApply is null && this.GetTemplateChild("BtnApply") is Button ba) { - _categoryList.ItemsSource = Unicode.CreateRangesList(SourceCategories as List); + _btnApply = ba; + ba.Click -= FilterAccept_Click; + ba.Click += FilterAccept_Click; } + } - private void FilterSelectAll_Click(object sender, RoutedEventArgs e) + public void OnOpening() + { + if (_categoryList.ItemsSource is IReadOnlyList list + && SourceCategories == _listSrc) { - foreach (var item in ((List)_categoryList.ItemsSource)) - item.IsSelected = true; + //foreach (var item in list) + // item.IsSelected = true; } - - private void FilterClear_Click(object sender, RoutedEventArgs e) + else { - foreach (var item in ((List)_categoryList.ItemsSource)) - item.IsSelected = false; + _listSrc = SourceCategories; + _categoryList.ItemsSource = Unicode.CreateRangesList(SourceCategories as List); } } + + private void FilterAccept_Click(object sender, RoutedEventArgs e) + { + AcceptClicked?.Invoke(this, (List)_categoryList.ItemsSource); + Flyout?.Hide(); + } + + private void FilterRefresh_Click(object sender, RoutedEventArgs e) + { + _categoryList.ItemsSource = Unicode.CreateRangesList(SourceCategories as List); + } + + private void FilterSelectAll_Click(object sender, RoutedEventArgs e) + { + foreach (var item in ((List)_categoryList.ItemsSource)) + item.IsSelected = true; + } + + private void FilterClear_Click(object sender, RoutedEventArgs e) + { + foreach (var item in ((List)_categoryList.ItemsSource)) + item.IsSelected = false; + } } diff --git a/CharacterMap/CharacterMap/Controls/CharacterGridView.cs b/CharacterMap/CharacterMap/Controls/CharacterGridView.cs index d3359e84..2f0b6d9a 100644 --- a/CharacterMap/CharacterMap/Controls/CharacterGridView.cs +++ b/CharacterMap/CharacterMap/Controls/CharacterGridView.cs @@ -9,507 +9,506 @@ using Windows.UI.Xaml.Markup; using Windows.UI.Xaml.Media; -namespace CharacterMap.Controls +namespace CharacterMap.Controls; + +internal class CharacterGridViewTemplateSettings { - internal class CharacterGridViewTemplateSettings - { - public FontFamily FontFamily { get; set; } - public DWriteFontFace FontFace { get; set; } - public TypographyFeatureInfo Typography { get; set;} - public bool ShowColorGlyphs { get; set; } - public double Size { get; set; } - public bool EnableReposition { get; set; } - public GlyphAnnotation Annotation { get; set; } - } + public FontFamily FontFamily { get; set; } + public DWriteFontFace FontFace { get; set; } + public TypographyFeatureInfo Typography { get; set; } + public bool ShowColorGlyphs { get; set; } + public double Size { get; set; } + public bool EnableReposition { get; set; } + public GlyphAnnotation Annotation { get; set; } +} - public class CharacterGridView : GridView - { - public event EventHandler ItemDoubleTapped; +public class CharacterGridView : GridView +{ + public event EventHandler ItemDoubleTapped; - #region Dependency Properties + #region Dependency Properties - #region ItemSize + #region ItemSize - public double ItemSize + public double ItemSize + { + get { return (double)GetValue(ItemSizeProperty); } + set { SetValue(ItemSizeProperty, value); } + } + + public static readonly DependencyProperty ItemSizeProperty = + DependencyProperty.Register(nameof(ItemSize), typeof(double), typeof(CharacterGridView), new PropertyMetadata(0d, (d, e) => { - get { return (double)GetValue(ItemSizeProperty); } - set { SetValue(ItemSizeProperty, value); } - } + ((CharacterGridView)d)._templateSettings.Size = (double)e.NewValue; + })); - public static readonly DependencyProperty ItemSizeProperty = - DependencyProperty.Register(nameof(ItemSize), typeof(double), typeof(CharacterGridView), new PropertyMetadata(0d, (d, e) => - { - ((CharacterGridView)d)._templateSettings.Size = (double)e.NewValue; - })); + #endregion - #endregion + #region ItemFontFamily - #region ItemFontFamily + public FontFamily ItemFontFamily + { + get { return (FontFamily)GetValue(ItemFontFamilyProperty); } + set { SetValue(ItemFontFamilyProperty, value); } + } - public FontFamily ItemFontFamily + public static readonly DependencyProperty ItemFontFamilyProperty = + DependencyProperty.Register(nameof(ItemFontFamily), typeof(FontFamily), typeof(CharacterGridView), new PropertyMetadata(null, (d, e) => { - get { return (FontFamily)GetValue(ItemFontFamilyProperty); } - set { SetValue(ItemFontFamilyProperty, value); } - } + ((CharacterGridView)d)._templateSettings.FontFamily = (FontFamily)e.NewValue; + })); - public static readonly DependencyProperty ItemFontFamilyProperty = - DependencyProperty.Register(nameof(ItemFontFamily), typeof(FontFamily), typeof(CharacterGridView), new PropertyMetadata(null, (d, e) => - { - ((CharacterGridView)d)._templateSettings.FontFamily = (FontFamily)e.NewValue; - })); + #endregion - #endregion + #region ItemFontFace - #region ItemFontFace + public DWriteFontFace ItemFontFace + { + get { return (DWriteFontFace)GetValue(ItemFontFaceProperty); } + set { SetValue(ItemFontFaceProperty, value); } + } - public DWriteFontFace ItemFontFace + public static readonly DependencyProperty ItemFontFaceProperty = + DependencyProperty.Register(nameof(ItemFontFace), typeof(DWriteFontFace), typeof(CharacterGridView), new PropertyMetadata(null, (d, e) => { - get { return (DWriteFontFace)GetValue(ItemFontFaceProperty); } - set { SetValue(ItemFontFaceProperty, value); } - } + ((CharacterGridView)d)._templateSettings.FontFace = (DWriteFontFace)e.NewValue; + })); - public static readonly DependencyProperty ItemFontFaceProperty = - DependencyProperty.Register(nameof(ItemFontFace), typeof(DWriteFontFace), typeof(CharacterGridView), new PropertyMetadata(null, (d, e) => - { - ((CharacterGridView)d)._templateSettings.FontFace = (DWriteFontFace)e.NewValue; - })); + #endregion - #endregion + #region ItemTypography - #region ItemTypography + public TypographyFeatureInfo ItemTypography + { + get { return (TypographyFeatureInfo)GetValue(ItemTypographyProperty); } + set { SetValue(ItemTypographyProperty, value); } + } - public TypographyFeatureInfo ItemTypography + public static readonly DependencyProperty ItemTypographyProperty = + DependencyProperty.Register(nameof(ItemTypography), typeof(TypographyFeatureInfo), typeof(CharacterGridView), new PropertyMetadata(null, (d, e) => { - get { return (TypographyFeatureInfo)GetValue(ItemTypographyProperty); } - set { SetValue(ItemTypographyProperty, value); } - } - - public static readonly DependencyProperty ItemTypographyProperty = - DependencyProperty.Register(nameof(ItemTypography), typeof(TypographyFeatureInfo), typeof(CharacterGridView), new PropertyMetadata(null, (d, e) => + if (d is CharacterGridView g && e.NewValue is TypographyFeatureInfo t) { - if (d is CharacterGridView g && e.NewValue is TypographyFeatureInfo t) - { - g._templateSettings.Typography = t; - g.UpdateTypographies(t); - } - })); + g._templateSettings.Typography = t; + g.UpdateTypographies(t); + } + })); - #endregion + #endregion - #region ShowColorGlyphs + #region ShowColorGlyphs - public bool ShowColorGlyphs - { - get { return (bool)GetValue(ShowColorGlyphsProperty); } - set { SetValue(ShowColorGlyphsProperty, value); } - } + public bool ShowColorGlyphs + { + get { return (bool)GetValue(ShowColorGlyphsProperty); } + set { SetValue(ShowColorGlyphsProperty, value); } + } - public static readonly DependencyProperty ShowColorGlyphsProperty = - DependencyProperty.Register(nameof(ShowColorGlyphs), typeof(bool), typeof(CharacterGridView), new PropertyMetadata(false, (d, e) => + public static readonly DependencyProperty ShowColorGlyphsProperty = + DependencyProperty.Register(nameof(ShowColorGlyphs), typeof(bool), typeof(CharacterGridView), new PropertyMetadata(false, (d, e) => + { + if (d is CharacterGridView g && e.NewValue is bool b) { - if (d is CharacterGridView g && e.NewValue is bool b) - { - g._templateSettings.ShowColorGlyphs = b; - g.UpdateColorsFonts(b); - } - })); + g._templateSettings.ShowColorGlyphs = b; + g.UpdateColorsFonts(b); + } + })); - #endregion + #endregion - #region ShowUnicodeDescription + #region ShowUnicodeDescription - public GlyphAnnotation ItemAnnotation - { - get { return (GlyphAnnotation)GetValue(ItemAnnotationProperty); } - set { SetValue(ItemAnnotationProperty, value); } - } + public GlyphAnnotation ItemAnnotation + { + get { return (GlyphAnnotation)GetValue(ItemAnnotationProperty); } + set { SetValue(ItemAnnotationProperty, value); } + } - // Using a DependencyProperty as the backing store for ItemAnnotation. This enables animation, styling, binding, etc... - public static readonly DependencyProperty ItemAnnotationProperty = - DependencyProperty.Register("ItemAnnotation", typeof(GlyphAnnotation), typeof(CharacterGridView), new PropertyMetadata(GlyphAnnotation.None, (d, e) => + // Using a DependencyProperty as the backing store for ItemAnnotation. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ItemAnnotationProperty = + DependencyProperty.Register("ItemAnnotation", typeof(GlyphAnnotation), typeof(CharacterGridView), new PropertyMetadata(GlyphAnnotation.None, (d, e) => + { + if (d is CharacterGridView g && e.NewValue is GlyphAnnotation b) { - if (d is CharacterGridView g && e.NewValue is GlyphAnnotation b) - { - g._templateSettings.Annotation = b; - g.UpdateUnicode(b); - } - })); + g._templateSettings.Annotation = b; + g.UpdateUnicode(b); + } + })); - #endregion + #endregion - #region EnableResizeAnimation + #region EnableResizeAnimation - public bool EnableResizeAnimation - { - get { return (bool)GetValue(EnableResizeAnimationProperty); } - set { SetValue(EnableResizeAnimationProperty, value); } - } + public bool EnableResizeAnimation + { + get { return (bool)GetValue(EnableResizeAnimationProperty); } + set { SetValue(EnableResizeAnimationProperty, value); } + } - public static readonly DependencyProperty EnableResizeAnimationProperty = - DependencyProperty.Register(nameof(EnableResizeAnimation), typeof(bool), typeof(CharacterGridView), new PropertyMetadata(false, (d, e) => + public static readonly DependencyProperty EnableResizeAnimationProperty = + DependencyProperty.Register(nameof(EnableResizeAnimation), typeof(bool), typeof(CharacterGridView), new PropertyMetadata(false, (d, e) => + { + if (d is CharacterGridView g && e.NewValue is bool b) { - if (d is CharacterGridView g && e.NewValue is bool b) - { - g._templateSettings.EnableReposition = b && CompositionFactory.UISettings.AnimationsEnabled; - g.UpdateAnimation(b); - } - })); + g._templateSettings.EnableReposition = b && CompositionFactory.UISettings.AnimationsEnabled; + g.UpdateAnimation(b); + } + })); - #endregion + #endregion - #region ItemFontVariant + #region ItemFontVariant - public FontVariant ItemFontVariant - { - get { return (FontVariant)GetValue(ItemFontVariantProperty); } - set { SetValue(ItemFontVariantProperty, value); } - } + public FontVariant ItemFontVariant + { + get { return (FontVariant)GetValue(ItemFontVariantProperty); } + set { SetValue(ItemFontVariantProperty, value); } + } - public static readonly DependencyProperty ItemFontVariantProperty = - DependencyProperty.Register(nameof(ItemFontVariant), typeof(FontVariant), typeof(CharacterGridView), new PropertyMetadata(null)); + public static readonly DependencyProperty ItemFontVariantProperty = + DependencyProperty.Register(nameof(ItemFontVariant), typeof(FontVariant), typeof(CharacterGridView), new PropertyMetadata(null)); - #endregion + #endregion - #endregion + #endregion - private XamlDirect _xamlDirect = null; + private XamlDirect _xamlDirect = null; - private CharacterGridViewTemplateSettings _templateSettings = null; + private CharacterGridViewTemplateSettings _templateSettings = null; - public CharacterGridView() - { - _xamlDirect = XamlDirect.GetDefault(); - _templateSettings = new CharacterGridViewTemplateSettings(); + public CharacterGridView() + { + _xamlDirect = XamlDirect.GetDefault(); + _templateSettings = new CharacterGridViewTemplateSettings(); - this.ContainerContentChanging += OnContainerContentChanging; - this.ChoosingItemContainer += OnChoosingItemContainer; - } + this.ContainerContentChanging += OnContainerContentChanging; + this.ChoosingItemContainer += OnChoosingItemContainer; + } - private class ItemTooltipData - { - public Character Char { get; set; } - public FontVariant Variant { get; set; } - public GridViewItem Container { get; set; } - } + private class ItemTooltipData + { + public Character Char { get; set; } + public FontVariant Variant { get; set; } + public GridViewItem Container { get; set; } + } - private void OnContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) + private void OnContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) + { + /* + * For performance reasons, we've forgone XAML bindings and + * will update everything in code + */ + if (!args.InRecycleQueue && args.ItemContainer is GridViewItem item) { - /* - * For performance reasons, we've forgone XAML bindings and - * will update everything in code - */ - if (!args.InRecycleQueue && args.ItemContainer is GridViewItem item) - { - Character c = ((Character)args.Item); - UpdateContainer(item, c); - args.Handled = true; + Character c = ((Character)args.Item); + UpdateContainer(item, c); + args.Handled = true; - item.DataContext = c; - item.DoubleTapped -= Item_DoubleTapped; - item.DoubleTapped += Item_DoubleTapped; + item.DataContext = c; + item.DoubleTapped -= Item_DoubleTapped; + item.DoubleTapped += Item_DoubleTapped; - // Set ToolTip - if (ItemFontVariant is not null) + // Set ToolTip + if (ItemFontVariant is not null) + { + if ((ToolTipService.GetToolTip(item) is ToolTip t) is false) { - if ((ToolTipService.GetToolTip(item) is ToolTip t) is false) + t = new(); + t.PlacementTarget = item; + t.VerticalOffset = 4; + t.Placement = Windows.UI.Xaml.Controls.Primitives.PlacementMode.Top; + t.Loaded += (d, e) => { - t = new(); - t.PlacementTarget = item; - t.VerticalOffset = 4; - t.Placement = Windows.UI.Xaml.Controls.Primitives.PlacementMode.Top; - t.Loaded += (d, e) => + if (d is ToolTip tt && tt.Tag is ItemTooltipData data) { - if (d is ToolTip tt && tt.Tag is ItemTooltipData data) - { - tt.PlacementRect = new (0, 0, data.Container.ActualWidth, data.Container.ActualHeight); - - // Do not use object initializer Constructor here, this will result in random NullReferenceExceptions. - // No idea why. - TextBlock t = new(); - t.TextWrapping = TextWrapping.Wrap; - string txt = data.Variant is not null - ? data.Variant.GetDescription(data.Char, allowUnihan: true) - : string.Empty; - t.Text = txt ?? data.Char.UnicodeString; - tt.Content = t; - } - }; - ToolTipService.SetToolTip(item, t); - } - t.Tag = new ItemTooltipData { Char = c, Container = item, Variant = ItemFontVariant }; + tt.PlacementRect = new(0, 0, data.Container.ActualWidth, data.Container.ActualHeight); + + // Do not use object initializer Constructor here, this will result in random NullReferenceExceptions. + // No idea why. + TextBlock t = new(); + t.TextWrapping = TextWrapping.Wrap; + string txt = data.Variant is not null + ? data.Variant.GetDescription(data.Char, allowUnihan: true) + : string.Empty; + t.Text = txt ?? data.Char.UnicodeString; + tt.Content = t; + } + }; + ToolTipService.SetToolTip(item, t); } + t.Tag = new ItemTooltipData { Char = c, Container = item, Variant = ItemFontVariant }; } + } - if (_templateSettings.EnableReposition) + if (_templateSettings.EnableReposition) + { + if (args.InRecycleQueue) { - if (args.InRecycleQueue) - { - PokeUIElementZIndex(args.ItemContainer); - } - else - { - var v = ElementCompositionPreview.GetElementVisual(args.ItemContainer); - v.ImplicitAnimations = CompositionFactory.GetRepositionCollection(v.Compositor); - } + PokeUIElementZIndex(args.ItemContainer); + } + else + { + var v = ElementCompositionPreview.GetElementVisual(args.ItemContainer); + v.ImplicitAnimations = CompositionFactory.GetRepositionCollection(v.Compositor); } } + } - private void Item_DoubleTapped(object sender, Windows.UI.Xaml.Input.DoubleTappedRoutedEventArgs e) + private void Item_DoubleTapped(object sender, Windows.UI.Xaml.Input.DoubleTappedRoutedEventArgs e) + { + if (sender is GridViewItem item) { - if (sender is GridViewItem item) - { - ItemDoubleTapped?.Invoke(sender, item.DataContext as Character); - } + ItemDoubleTapped?.Invoke(sender, item.DataContext as Character); } + } - #region Item Template Handling + #region Item Template Handling - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void UpdateContainer(GridViewItem item, Character c) - { - // Perf considerations: - // 1 - Batch rendering updates by suspending rendering until all properties are set - // 2 - Use XAML direct to set new properties, rather than through DP's - // 3 - Access any required data properties from parents through normal properties, - // not DP's - DP access can be order of magnitudes slower. - // Note : This will be faster via C++ as it avoids all marshalling costs. - // Note: For more improved performance, do **not** use XAML ItemTemplate. - // Create entire template via XamlDirect, and never directly reference the - // WinRT XAML object. - - // Assumed Structure: - // -- Grid - // -- TextBlock - // -- TextBlock - - XamlBindingHelper.SuspendRendering(item); - - IXamlDirectObject go = _xamlDirect.GetXamlDirectObject(item.ContentTemplateRoot); - - _xamlDirect.SetObjectProperty(go, XamlPropertyIndex.FrameworkElement_Tag, c); - _xamlDirect.SetDoubleProperty(go, XamlPropertyIndex.FrameworkElement_Width, _templateSettings.Size); - _xamlDirect.SetDoubleProperty(go, XamlPropertyIndex.FrameworkElement_Height, _templateSettings.Size); - - IXamlDirectObject cld = _xamlDirect.GetXamlDirectObjectProperty(go, XamlPropertyIndex.Panel_Children); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void UpdateContainer(GridViewItem item, Character c) + { + // Perf considerations: + // 1 - Batch rendering updates by suspending rendering until all properties are set + // 2 - Use XAML direct to set new properties, rather than through DP's + // 3 - Access any required data properties from parents through normal properties, + // not DP's - DP access can be order of magnitudes slower. + // Note : This will be faster via C++ as it avoids all marshalling costs. + // Note: For more improved performance, do **not** use XAML ItemTemplate. + // Create entire template via XamlDirect, and never directly reference the + // WinRT XAML object. + + // Assumed Structure: + // -- Grid + // -- TextBlock + // -- TextBlock + + XamlBindingHelper.SuspendRendering(item); + + IXamlDirectObject go = _xamlDirect.GetXamlDirectObject(item.ContentTemplateRoot); + + _xamlDirect.SetObjectProperty(go, XamlPropertyIndex.FrameworkElement_Tag, c); + _xamlDirect.SetDoubleProperty(go, XamlPropertyIndex.FrameworkElement_Width, _templateSettings.Size); + _xamlDirect.SetDoubleProperty(go, XamlPropertyIndex.FrameworkElement_Height, _templateSettings.Size); + + IXamlDirectObject cld = _xamlDirect.GetXamlDirectObjectProperty(go, XamlPropertyIndex.Panel_Children); #if DX { - var t = (DirectText)((Grid)item.ContentTemplateRoot).Children[0]; ; - SetGlyphProperties(t, _templateSettings, c); + var t = (DirectText)((Grid)item.ContentTemplateRoot).Children[0]; ; + SetGlyphProperties(t, _templateSettings, c); } #else -{ + { IXamlDirectObject o = _xamlDirect.GetXamlDirectObjectFromCollectionAt(cld, 0); SetGlyphProperties(_xamlDirect, o, _templateSettings, c); -} + } #endif - IXamlDirectObject o2 = _xamlDirect.GetXamlDirectObjectFromCollectionAt(cld, 1); - if (o2 != null) + IXamlDirectObject o2 = _xamlDirect.GetXamlDirectObjectFromCollectionAt(cld, 1); + if (o2 != null) + { + switch (_templateSettings.Annotation) { - switch(_templateSettings.Annotation) - { - case GlyphAnnotation.None: - _xamlDirect.SetEnumProperty(o2, XamlPropertyIndex.UIElement_Visibility, 1); - break; - default: - _xamlDirect.SetStringProperty(o2, XamlPropertyIndex.TextBlock_Text, c.GetAnnotation(_templateSettings.Annotation)); - _xamlDirect.SetEnumProperty(o2, XamlPropertyIndex.UIElement_Visibility, 0); - break; - } + case GlyphAnnotation.None: + _xamlDirect.SetEnumProperty(o2, XamlPropertyIndex.UIElement_Visibility, 1); + break; + default: + _xamlDirect.SetStringProperty(o2, XamlPropertyIndex.TextBlock_Text, c.GetAnnotation(_templateSettings.Annotation)); + _xamlDirect.SetEnumProperty(o2, XamlPropertyIndex.UIElement_Visibility, 0); + break; } - - XamlBindingHelper.ResumeRendering(item); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void SetGlyphProperties(XamlDirect xamlDirect, IXamlDirectObject o, CharacterGridViewTemplateSettings templateSettings, Character c) - { - if (o == null || templateSettings.FontFace is null) - return; + XamlBindingHelper.ResumeRendering(item); + } - xamlDirect.SetObjectProperty(o, XamlPropertyIndex.TextBlock_FontFamily, templateSettings.FontFamily); - xamlDirect.SetEnumProperty(o, XamlPropertyIndex.TextBlock_FontStretch, (uint)templateSettings.FontFace.Properties.Stretch); - xamlDirect.SetEnumProperty(o, XamlPropertyIndex.TextBlock_FontStyle, (uint)templateSettings.FontFace.Properties.Style); - xamlDirect.SetObjectProperty(o, XamlPropertyIndex.TextBlock_FontWeight, templateSettings.FontFace.Properties.Weight); - xamlDirect.SetBooleanProperty(o, XamlPropertyIndex.TextBlock_IsColorFontEnabled, templateSettings.ShowColorGlyphs); - xamlDirect.SetDoubleProperty(o, XamlPropertyIndex.TextBlock_FontSize, templateSettings.Size / 2d); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void SetGlyphProperties(XamlDirect xamlDirect, IXamlDirectObject o, CharacterGridViewTemplateSettings templateSettings, Character c) + { + if (o == null || templateSettings.FontFace is null) + return; - UpdateColorFont(xamlDirect, null, o, templateSettings.ShowColorGlyphs); - UpdateTypography(xamlDirect, o, templateSettings.Typography); + xamlDirect.SetObjectProperty(o, XamlPropertyIndex.TextBlock_FontFamily, templateSettings.FontFamily); + xamlDirect.SetEnumProperty(o, XamlPropertyIndex.TextBlock_FontStretch, (uint)templateSettings.FontFace.Properties.Stretch); + xamlDirect.SetEnumProperty(o, XamlPropertyIndex.TextBlock_FontStyle, (uint)templateSettings.FontFace.Properties.Style); + xamlDirect.SetObjectProperty(o, XamlPropertyIndex.TextBlock_FontWeight, templateSettings.FontFace.Properties.Weight); + xamlDirect.SetBooleanProperty(o, XamlPropertyIndex.TextBlock_IsColorFontEnabled, templateSettings.ShowColorGlyphs); + xamlDirect.SetDoubleProperty(o, XamlPropertyIndex.TextBlock_FontSize, templateSettings.Size / 2d); - xamlDirect.SetStringProperty(o, XamlPropertyIndex.TextBlock_Text, c.Char); - } + UpdateColorFont(xamlDirect, null, o, templateSettings.ShowColorGlyphs); + UpdateTypography(xamlDirect, o, templateSettings.Typography); - internal static void SetGlyphProperties(DirectText o, CharacterGridViewTemplateSettings templateSettings, Character c) - { - if (o == null) - return; - - o.FontFamily = templateSettings.FontFamily; - o.FontFace = templateSettings.FontFace; - o.FontStretch = templateSettings.FontFace.Properties.Stretch; - o.FontStyle = templateSettings.FontFace.Properties.Style; - o.FontWeight = templateSettings.FontFace.Properties.Weight; - o.IsColorFontEnabled = templateSettings.ShowColorGlyphs; - o.FontSize = templateSettings.Size / 2d; - o.Typography = templateSettings.Typography; - - o.Text = c.Char; - } + xamlDirect.SetStringProperty(o, XamlPropertyIndex.TextBlock_Text, c.Char); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void UpdateColorFont(XamlDirect xamlDirect, TextBlock block, IXamlDirectObject xd, bool value) - { - if (xd != null) - xamlDirect.SetBooleanProperty(xd, XamlPropertyIndex.TextBlock_IsColorFontEnabled, value); - else - block.IsColorFontEnabled = value; - } + internal static void SetGlyphProperties(DirectText o, CharacterGridViewTemplateSettings templateSettings, Character c) + { + if (o == null) + return; + + o.FontFamily = templateSettings.FontFamily; + o.FontFace = templateSettings.FontFace; + o.FontStretch = templateSettings.FontFace.Properties.Stretch; + o.FontStyle = templateSettings.FontFace.Properties.Style; + o.FontWeight = templateSettings.FontFace.Properties.Weight; + o.IsColorFontEnabled = templateSettings.ShowColorGlyphs; + o.FontSize = templateSettings.Size / 2d; + o.Typography = templateSettings.Typography; + + o.Text = c.Char; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UpdateTypography(XamlDirect xamlDirect, IXamlDirectObject o, TypographyFeatureInfo info) - { - CanvasTypographyFeatureName f = info == null ? CanvasTypographyFeatureName.None : info.Feature; - TypographyBehavior.SetTypography(o, f, xamlDirect); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void UpdateColorFont(XamlDirect xamlDirect, TextBlock block, IXamlDirectObject xd, bool value) + { + if (xd != null) + xamlDirect.SetBooleanProperty(xd, XamlPropertyIndex.TextBlock_IsColorFontEnabled, value); + else + block.IsColorFontEnabled = value; + } - void UpdateColorsFonts(bool value) - { - if (ItemsSource == null || ItemsPanelRoot == null) - return; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UpdateTypography(XamlDirect xamlDirect, IXamlDirectObject o, TypographyFeatureInfo info) + { + CanvasTypographyFeatureName f = info == null ? CanvasTypographyFeatureName.None : info.Feature; + TypographyBehavior.SetTypography(o, f, xamlDirect); + } - foreach (GridViewItem item in ItemsPanelRoot.Children.OfType()) + void UpdateColorsFonts(bool value) + { + if (ItemsSource == null || ItemsPanelRoot == null) + return; + + foreach (GridViewItem item in ItemsPanelRoot.Children.OfType()) + { + if (_xamlDirect.GetXamlDirectObject(item.ContentTemplateRoot) is IXamlDirectObject root) { - if (_xamlDirect.GetXamlDirectObject(item.ContentTemplateRoot) is IXamlDirectObject root) - { - var childs = _xamlDirect.GetXamlDirectObjectProperty(root, XamlPropertyIndex.Panel_Children); - IXamlDirectObject tb = _xamlDirect.GetXamlDirectObjectFromCollectionAt(childs, 0); - UpdateColorFont(_xamlDirect, null, tb, value); - } + var childs = _xamlDirect.GetXamlDirectObjectProperty(root, XamlPropertyIndex.Panel_Children); + IXamlDirectObject tb = _xamlDirect.GetXamlDirectObjectFromCollectionAt(childs, 0); + UpdateColorFont(_xamlDirect, null, tb, value); } } + } - void UpdateTypographies(TypographyFeatureInfo info) - { - if (ItemsSource == null || ItemsPanelRoot == null) - return; + void UpdateTypographies(TypographyFeatureInfo info) + { + if (ItemsSource == null || ItemsPanelRoot == null) + return; - foreach (GridViewItem item in ItemsPanelRoot.Children.OfType()) - { + foreach (GridViewItem item in ItemsPanelRoot.Children.OfType()) + { #if DX { - if (item.ContentTemplateRoot is Grid g) - { - DirectText tb = (DirectText)g.Children[0]; - tb.Typography = info; - } + if (item.ContentTemplateRoot is Grid g) + { + DirectText tb = (DirectText)g.Children[0]; + tb.Typography = info; + } } #else -{ + { if (_xamlDirect.GetXamlDirectObject(item.ContentTemplateRoot) is IXamlDirectObject root) { var childs = _xamlDirect.GetXamlDirectObjectProperty(root, XamlPropertyIndex.Panel_Children); IXamlDirectObject tb = _xamlDirect.GetXamlDirectObjectFromCollectionAt(childs, 0); UpdateTypography(_xamlDirect, tb, info); } -} -#endif } +#endif } + } - void UpdateUnicode(GlyphAnnotation value) - { - if (ItemsSource == null || ItemsPanelRoot == null) - return; + void UpdateUnicode(GlyphAnnotation value) + { + if (ItemsSource == null || ItemsPanelRoot == null) + return; - foreach (GridViewItem item in ItemsPanelRoot.Children.OfType()) + foreach (GridViewItem item in ItemsPanelRoot.Children.OfType()) + { + if (_xamlDirect.GetXamlDirectObject(item.ContentTemplateRoot) is IXamlDirectObject root) { - if (_xamlDirect.GetXamlDirectObject(item.ContentTemplateRoot) is IXamlDirectObject root) + if (_xamlDirect.GetObjectProperty(root, XamlPropertyIndex.FrameworkElement_Tag) is Character c) { - if (_xamlDirect.GetObjectProperty(root, XamlPropertyIndex.FrameworkElement_Tag) is Character c) - { - var childs = _xamlDirect.GetXamlDirectObjectProperty(root, XamlPropertyIndex.Panel_Children); - IXamlDirectObject tb = _xamlDirect.GetXamlDirectObjectFromCollectionAt(childs, 1); - _xamlDirect.SetStringProperty(tb, XamlPropertyIndex.TextBlock_Text, c.GetAnnotation(value)); - _xamlDirect.SetEnumProperty(tb, XamlPropertyIndex.UIElement_Visibility, (uint)(value != GlyphAnnotation.None ? 0 : 1)); - } + var childs = _xamlDirect.GetXamlDirectObjectProperty(root, XamlPropertyIndex.Panel_Children); + IXamlDirectObject tb = _xamlDirect.GetXamlDirectObjectFromCollectionAt(childs, 1); + _xamlDirect.SetStringProperty(tb, XamlPropertyIndex.TextBlock_Text, c.GetAnnotation(value)); + _xamlDirect.SetEnumProperty(tb, XamlPropertyIndex.UIElement_Visibility, (uint)(value != GlyphAnnotation.None ? 0 : 1)); } - - //if (item.ContentTemplateRoot is Grid g) - //{ - // if (g.Tag is Character c) - // { - // TextBlock tb = (TextBlock)g.Children[1]; - // tb.Text = c.GetAnnotation(value); - // tb.SetVisible(value != GlyphAnnotation.None); - // } - //} } + + //if (item.ContentTemplateRoot is Grid g) + //{ + // if (g.Tag is Character c) + // { + // TextBlock tb = (TextBlock)g.Children[1]; + // tb.Text = c.GetAnnotation(value); + // tb.SetVisible(value != GlyphAnnotation.None); + // } + //} } + } - public void UpdateSize(double value) - { - ItemSize = value; - if (this.Items.Count == 0 || ItemsPanelRoot == null) - return; + public void UpdateSize(double value) + { + ItemSize = value; + if (this.Items.Count == 0 || ItemsPanelRoot == null) + return; - foreach (GridViewItem item in ItemsPanelRoot.Children.OfType()) + foreach (GridViewItem item in ItemsPanelRoot.Children.OfType()) + { + if (_xamlDirect.GetXamlDirectObject(item.ContentTemplateRoot) is IXamlDirectObject root) { - if (_xamlDirect.GetXamlDirectObject(item.ContentTemplateRoot) is IXamlDirectObject root) - { - _xamlDirect.SetDoubleProperty(root, XamlPropertyIndex.FrameworkElement_Width, value); - _xamlDirect.SetDoubleProperty(root, XamlPropertyIndex.FrameworkElement_Height, value); - var childs = _xamlDirect.GetXamlDirectObjectProperty(root, XamlPropertyIndex.Panel_Children); - IXamlDirectObject tb = _xamlDirect.GetXamlDirectObjectFromCollectionAt(childs, 0); - _xamlDirect.SetDoubleProperty(tb, XamlPropertyIndex.Control_FontSize, value / 2d); - } - - //if (item.ContentTemplateRoot is Grid g) - //{ - // g.Width = value; - // g.Height = value; - // ((TextBlock)g.Children[0]).FontSize = value / 2d; - //} + _xamlDirect.SetDoubleProperty(root, XamlPropertyIndex.FrameworkElement_Width, value); + _xamlDirect.SetDoubleProperty(root, XamlPropertyIndex.FrameworkElement_Height, value); + var childs = _xamlDirect.GetXamlDirectObjectProperty(root, XamlPropertyIndex.Panel_Children); + IXamlDirectObject tb = _xamlDirect.GetXamlDirectObjectFromCollectionAt(childs, 0); + _xamlDirect.SetDoubleProperty(tb, XamlPropertyIndex.Control_FontSize, value / 2d); } + + //if (item.ContentTemplateRoot is Grid g) + //{ + // g.Width = value; + // g.Height = value; + // ((TextBlock)g.Children[0]).FontSize = value / 2d; + //} } + } - #endregion + #endregion - #region Reposition Animation + #region Reposition Animation - private void OnChoosingItemContainer(ListViewBase sender, ChoosingItemContainerEventArgs args) + private void OnChoosingItemContainer(ListViewBase sender, ChoosingItemContainerEventArgs args) + { + if (_templateSettings.EnableReposition && args.ItemContainer != null) { - if (_templateSettings.EnableReposition && args.ItemContainer != null) - { - PokeUIElementZIndex(args.ItemContainer); - } + PokeUIElementZIndex(args.ItemContainer); } + } - private void UpdateAnimation(bool newValue) - { - if (this.ItemsPanelRoot == null) - return; - - foreach (var item in this.ItemsPanelRoot.Children) - { - var v = ElementCompositionPreview.GetElementVisual(item); - v.ImplicitAnimations = newValue ? CompositionFactory.GetRepositionCollection(v.Compositor) : null; - } - } + private void UpdateAnimation(bool newValue) + { + if (this.ItemsPanelRoot == null) + return; - private void PokeUIElementZIndex(UIElement e) + foreach (var item in this.ItemsPanelRoot.Children) { - CompositionFactory.PokeUIElementZIndex(e, _xamlDirect); + var v = ElementCompositionPreview.GetElementVisual(item); + v.ImplicitAnimations = newValue ? CompositionFactory.GetRepositionCollection(v.Compositor) : null; } + } - #endregion + private void PokeUIElementZIndex(UIElement e) + { + CompositionFactory.PokeUIElementZIndex(e, _xamlDirect); } + + #endregion } \ No newline at end of file diff --git a/CharacterMap/CharacterMap/Controls/CharacterPicker.cs b/CharacterMap/CharacterMap/Controls/CharacterPicker.cs index 604b7088..57f67ddb 100644 --- a/CharacterMap/CharacterMap/Controls/CharacterPicker.cs +++ b/CharacterMap/CharacterMap/Controls/CharacterPicker.cs @@ -1,89 +1,79 @@ -using CharacterMap.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; -using Windows.UI.Xaml; +using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Data; -using Windows.UI.Xaml.Documents; -using Windows.UI.Xaml.Input; -using Windows.UI.Xaml.Media; -namespace CharacterMap.Controls -{ - public sealed class CharacterPicker : Control - { - public event EventHandler CharacterSelected; +namespace CharacterMap.Controls; - public CharacterRenderingOptions Options - { - get { return (CharacterRenderingOptions)GetValue(OptionsProperty); } - set { SetValue(OptionsProperty, value); } - } - - public static readonly DependencyProperty OptionsProperty = - DependencyProperty.Register(nameof(Options), typeof(CharacterRenderingOptions), typeof(CharacterPicker), new PropertyMetadata(null)); +public sealed class CharacterPicker : Control +{ + public event EventHandler CharacterSelected; - private Flyout _parent = null; + public CharacterRenderingOptions Options + { + get { return (CharacterRenderingOptions)GetValue(OptionsProperty); } + set { SetValue(OptionsProperty, value); } + } - public CharacterPicker() - { - this.DefaultStyleKey = typeof(CharacterPicker); - } + public static readonly DependencyProperty OptionsProperty = + DependencyProperty.Register(nameof(Options), typeof(CharacterRenderingOptions), typeof(CharacterPicker), new PropertyMetadata(null)); - public CharacterPicker(Flyout parent, CharacterRenderingOptions options) : this() - { - _parent = parent; - Options = options; - } + private Flyout _parent = null; - CharacterGridView _itemsGridView = null; + public CharacterPicker() + { + this.DefaultStyleKey = typeof(CharacterPicker); + } - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); + public CharacterPicker(Flyout parent, CharacterRenderingOptions options) : this() + { + _parent = parent; + Options = options; + } - if (_itemsGridView is not null) - _itemsGridView.ItemDoubleTapped -= ItemsGridView_ItemDoubleTapped; + CharacterGridView _itemsGridView = null; - if (this.GetTemplateChild("ItemsGridView") is CharacterGridView g) - { - _itemsGridView = g; - g.ItemDoubleTapped -= ItemsGridView_ItemDoubleTapped; - g.ItemDoubleTapped += ItemsGridView_ItemDoubleTapped; - } + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); - if (this.GetTemplateChild("AddButton") is Button b) - { - b.Click -= Add_Click; - b.Click += Add_Click; - } + if (_itemsGridView is not null) + _itemsGridView.ItemDoubleTapped -= ItemsGridView_ItemDoubleTapped; - if (this.GetTemplateChild("CloseButton") is Button c) - { - c.Click -= Close_Click; - c.Click += Close_Click; - } + if (this.GetTemplateChild("ItemsGridView") is CharacterGridView g) + { + _itemsGridView = g; + g.ItemDoubleTapped -= ItemsGridView_ItemDoubleTapped; + g.ItemDoubleTapped += ItemsGridView_ItemDoubleTapped; } - private void Add_Click(object sender, RoutedEventArgs e) + + if (this.GetTemplateChild("AddButton") is Button b) { - if (_itemsGridView.SelectedItem is Character c) - CharacterSelected?.Invoke(this, c); + b.Click -= Add_Click; + b.Click += Add_Click; } - private void Close_Click(object sender, RoutedEventArgs e) + if (this.GetTemplateChild("CloseButton") is Button c) { - if (_parent is not null) - { - _parent.Hide(); - _parent = null; - } + c.Click -= Close_Click; + c.Click += Close_Click; } + } + private void Add_Click(object sender, RoutedEventArgs e) + { + if (_itemsGridView.SelectedItem is Character c) + CharacterSelected?.Invoke(this, c); + } - private void ItemsGridView_ItemDoubleTapped(object sender, Character e) + private void Close_Click(object sender, RoutedEventArgs e) + { + if (_parent is not null) { - CharacterSelected?.Invoke(this, e); + _parent.Hide(); + _parent = null; } } + + private void ItemsGridView_ItemDoubleTapped(object sender, Character e) + { + CharacterSelected?.Invoke(this, e); + } } diff --git a/CharacterMap/CharacterMap/Controls/CharacterPickerButton.cs b/CharacterMap/CharacterMap/Controls/CharacterPickerButton.cs index d4e50844..0e016434 100644 --- a/CharacterMap/CharacterMap/Controls/CharacterPickerButton.cs +++ b/CharacterMap/CharacterMap/Controls/CharacterPickerButton.cs @@ -1,118 +1,108 @@ -using CharacterMap.Models; -using Microsoft.UI.Xaml.Controls; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; -using Windows.UI.Xaml; +using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Data; -using Windows.UI.Xaml.Documents; -using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; -namespace CharacterMap.Controls +namespace CharacterMap.Controls; + +public sealed class CharacterPickerButton : ContentControl { - public sealed class CharacterPickerButton : ContentControl - { - private static PropertyMetadata NULL_META = new (null); + private static PropertyMetadata NULL_META = new(null); + + public FlyoutPlacementMode Placement + { + get { return (FlyoutPlacementMode)GetValue(PlacementProperty); } + set { SetValue(PlacementProperty, value); } + } - public FlyoutPlacementMode Placement + public static readonly DependencyProperty PlacementProperty = + DependencyProperty.Register(nameof(Placement), typeof(FlyoutPlacementMode), typeof(CharacterPickerButton), new PropertyMetadata(FlyoutPlacementMode.Bottom, (d, e) => { - get { return (FlyoutPlacementMode)GetValue(PlacementProperty); } - set { SetValue(PlacementProperty, value); } - } + ((CharacterPickerButton)d).UpdatePlacement(); + })); - public static readonly DependencyProperty PlacementProperty = - DependencyProperty.Register(nameof(Placement), typeof(FlyoutPlacementMode), typeof(CharacterPickerButton), new PropertyMetadata(FlyoutPlacementMode.Bottom, (d,e) => - { - ((CharacterPickerButton)d).UpdatePlacement(); - })); + public event EventHandler CharacterSelected; - public event EventHandler CharacterSelected; + public Control Target + { + get { return (Control)GetValue(TargetProperty); } + set { SetValue(TargetProperty, value); } + } - public Control Target - { - get { return (Control)GetValue(TargetProperty); } - set { SetValue(TargetProperty, value); } - } + public static readonly DependencyProperty TargetProperty = + DependencyProperty.Register(nameof(Target), typeof(Control), typeof(CharacterPickerButton), NULL_META); - public static readonly DependencyProperty TargetProperty = - DependencyProperty.Register(nameof(Target), typeof(Control), typeof(CharacterPickerButton), NULL_META); + public CharacterRenderingOptions Options + { + get { return (CharacterRenderingOptions)GetValue(OptionsProperty); } + set { SetValue(OptionsProperty, value); } + } - public CharacterRenderingOptions Options - { - get { return (CharacterRenderingOptions)GetValue(OptionsProperty); } - set { SetValue(OptionsProperty, value); } - } + public static readonly DependencyProperty OptionsProperty = + DependencyProperty.Register(nameof(Options), typeof(CharacterRenderingOptions), typeof(CharacterPickerButton), NULL_META); - public static readonly DependencyProperty OptionsProperty = - DependencyProperty.Register(nameof(Options), typeof(CharacterRenderingOptions), typeof(CharacterPickerButton), NULL_META); + public CharacterPickerButton() + { + this.DefaultStyleKey = typeof(CharacterPickerButton); + } - public CharacterPickerButton() - { - this.DefaultStyleKey = typeof(CharacterPickerButton); - } + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); - protected override void OnApplyTemplate() + if (this.GetTemplateChild("RootButton") is Button b) { - base.OnApplyTemplate(); - - if (this.GetTemplateChild("RootButton") is Button b) + if (b.Flyout is { } flyout) { - if (b.Flyout is { } flyout) - { - flyout.Opening -= Flyout_Opening; - flyout.Opening += Flyout_Opening; - } + flyout.Opening -= Flyout_Opening; + flyout.Opening += Flyout_Opening; } - - UpdatePlacement(); } - void UpdatePlacement() - { - VisualStateManager.GoToState(this, Placement == FlyoutPlacementMode.BottomEdgeAlignedRight ? "RightPlacementState" : "DefaultPlacementState", false); - } + UpdatePlacement(); + } - private void Flyout_Opening(object sender, object e) + void UpdatePlacement() + { + VisualStateManager.GoToState(this, Placement == FlyoutPlacementMode.BottomEdgeAlignedRight ? "RightPlacementState" : "DefaultPlacementState", false); + } + + private void Flyout_Opening(object sender, object e) + { + if (sender is Flyout { } flyout) { - if (sender is Flyout { } flyout) - { - if (flyout.Content is CharacterPicker p) - p.CharacterSelected -= Picker_CharacterSelected; + if (flyout.Content is CharacterPicker p) + p.CharacterSelected -= Picker_CharacterSelected; - p = new CharacterPicker(flyout, Options); - p.CharacterSelected += Picker_CharacterSelected; - flyout.Content = p; - } + p = new CharacterPicker(flyout, Options); + p.CharacterSelected += Picker_CharacterSelected; + flyout.Content = p; } + } - private void Picker_CharacterSelected(object sender, Character e) + private void Picker_CharacterSelected(object sender, Character e) + { + if (Target is AutoSuggestBox ab) { - if (Target is AutoSuggestBox ab) + if (ab.GetFirstDescendantOfType() is { } t && t.SelectionStart > -1) { - if (ab.GetFirstDescendantOfType() is { } t && t.SelectionStart > -1) - { - string txt = ab.Text; - int start = t.SelectionStart; - if (t.SelectionLength > 0) - txt = txt.Remove(t.SelectionStart, t.SelectionLength); - - ab.Text = txt.Insert(t.SelectionStart, e.Char); - t.SelectionStart = start + 1; - } + string txt = ab.Text; + int start = t.SelectionStart; + if (t.SelectionLength > 0) + txt = txt.Remove(t.SelectionStart, t.SelectionLength); + + ab.Text = txt.Insert(t.SelectionStart, e.Char); + t.SelectionStart = start + 1; } - if (Target is SuggestionBox sb) - sb.Text += e.Char; - else if (Target is TextBox box) - box.Text += e.Char; - else if (Target is ComboBox c) - c.Text += e.Char; - - CharacterSelected?.Invoke(this, e); } + if (Target is SuggestionBox sb) + sb.Text += e.Char; + else if (Target is TextBox box) + box.Text += e.Char; + else if (Target is ComboBox c) + c.Text += e.Char; + + CharacterSelected?.Invoke(this, e); } } diff --git a/CharacterMap/CharacterMap/Controls/ContentGroup.cs b/CharacterMap/CharacterMap/Controls/ContentGroup.cs index ff4350a1..8dbfbb23 100644 --- a/CharacterMap/CharacterMap/Controls/ContentGroup.cs +++ b/CharacterMap/CharacterMap/Controls/ContentGroup.cs @@ -1,6 +1,5 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Input; namespace CharacterMap.Controls; diff --git a/CharacterMap/CharacterMap/Controls/CreateCollectionDialog.xaml.cs b/CharacterMap/CharacterMap/Controls/CreateCollectionDialog.xaml.cs index f16f5fe7..e4f73810 100644 --- a/CharacterMap/CharacterMap/Controls/CreateCollectionDialog.xaml.cs +++ b/CharacterMap/CharacterMap/Controls/CreateCollectionDialog.xaml.cs @@ -1,112 +1,90 @@ -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.Services; -using CharacterMap.ViewModels; -using CommunityToolkit.Mvvm.DependencyInjection; -using CommunityToolkit.Mvvm.Messaging; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; -using System.Threading.Tasks; -using Windows.Foundation; -using Windows.Foundation.Collections; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Data; -using Windows.UI.Xaml.Input; -using Windows.UI.Xaml.Media; -using Windows.UI.Xaml.Navigation; - -namespace CharacterMap.Controls +using Windows.UI.Xaml.Controls; + +namespace CharacterMap.Controls; + +public class CreateCollectionDialogTemplateSettings : ViewModelBase { - public class CreateCollectionDialogTemplateSettings : ViewModelBase + private string _collectionTitle; + public string CollectionTitle { - private string _collectionTitle; - public string CollectionTitle - { - get => _collectionTitle; - set { if (Set(ref _collectionTitle, value)) OnCollectionTitleChanged(); } - } - - private bool _isCollectionTitleValid; - public bool IsCollectionTitleValid - { - get => _isCollectionTitleValid; - private set => Set(ref _isCollectionTitleValid, value); - } + get => _collectionTitle; + set { if (Set(ref _collectionTitle, value)) OnCollectionTitleChanged(); } + } - private void OnCollectionTitleChanged() - { - IsCollectionTitleValid = !string.IsNullOrWhiteSpace(CollectionTitle); - } + private bool _isCollectionTitleValid; + public bool IsCollectionTitleValid + { + get => _isCollectionTitleValid; + private set => Set(ref _isCollectionTitleValid, value); } - public sealed partial class CreateCollectionDialog : ContentDialog + private void OnCollectionTitleChanged() { - public CreateCollectionDialogTemplateSettings TemplateSettings { get; } + IsCollectionTitleValid = !string.IsNullOrWhiteSpace(CollectionTitle); + } +} + +public sealed partial class CreateCollectionDialog : ContentDialog +{ + public CreateCollectionDialogTemplateSettings TemplateSettings { get; } + + public bool IsRenameMode { get; } - public bool IsRenameMode { get; } + public object Result { get; private set; } - public object Result { get; private set; } + private UserFontCollection _collection = null; - private UserFontCollection _collection = null; + public CreateCollectionDialog(UserFontCollection collection = null) + { + _collection = collection; + TemplateSettings = new CreateCollectionDialogTemplateSettings(); + this.InitializeComponent(); - public CreateCollectionDialog(UserFontCollection collection = null) + if (_collection != null) { - _collection = collection; - TemplateSettings = new CreateCollectionDialogTemplateSettings(); - this.InitializeComponent(); - - if (_collection != null) - { - IsRenameMode = true; - this.Title = Localization.Get("DigRenameCollection/Title"); - this.PrimaryButtonText = Localization.Get("DigRenameCollection/PrimaryButtonText"); - TemplateSettings.CollectionTitle = _collection.Name; - } + IsRenameMode = true; + this.Title = Localization.Get("DigRenameCollection/Title"); + this.PrimaryButtonText = Localization.Get("DigRenameCollection/PrimaryButtonText"); + TemplateSettings.CollectionTitle = _collection.Name; } + } + + + private async void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) + { + var d = args.GetDeferral(); + var collections = Ioc.Default.GetService(); - private async void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) + if (IsRenameMode) { - var d = args.GetDeferral(); - - var collections = Ioc.Default.GetService(); - - if (IsRenameMode) - { - this.IsPrimaryButtonEnabled = false; - this.IsSecondaryButtonEnabled = false; - InputBox.IsEnabled = false; - - await collections.RenameCollectionAsync(TemplateSettings.CollectionTitle, _collection); - d.Complete(); - - await Task.Yield(); - WeakReferenceMessenger.Default.Send(new CollectionsUpdatedMessage()); - } - else - { - AddToCollectionResult result = null; - UserFontCollection collection = await collections.CreateCollectionAsync(TemplateSettings.CollectionTitle); - - if (this.DataContext is InstalledFont font) - result = await collections.AddToCollectionAsync(font, collection); - else if (this.DataContext is IList fonts) - result = await collections.AddToCollectionAsync(fonts, collection); - - Result = result; - d.Complete(); - await Task.Yield(); - if (result is not null && result.Success) - WeakReferenceMessenger.Default.Send(new AppNotificationMessage(true, result)); - - } + this.IsPrimaryButtonEnabled = false; + this.IsSecondaryButtonEnabled = false; + InputBox.IsEnabled = false; + + await collections.RenameCollectionAsync(TemplateSettings.CollectionTitle, _collection); + d.Complete(); + + await Task.Yield(); + WeakReferenceMessenger.Default.Send(new CollectionsUpdatedMessage()); + } + else + { + AddToCollectionResult result = null; + UserFontCollection collection = await collections.CreateCollectionAsync(TemplateSettings.CollectionTitle); + + if (this.DataContext is InstalledFont font) + result = await collections.AddToCollectionAsync(font, collection); + else if (this.DataContext is IList fonts) + result = await collections.AddToCollectionAsync(fonts, collection); + + Result = result; + d.Complete(); + await Task.Yield(); + if (result is not null && result.Success) + WeakReferenceMessenger.Default.Send(new AppNotificationMessage(true, result)); + } } } diff --git a/CharacterMap/CharacterMap/Controls/DirectTextBlock.cs b/CharacterMap/CharacterMap/Controls/DirectTextBlock.cs index 2aec676a..5f7a51af 100644 --- a/CharacterMap/CharacterMap/Controls/DirectTextBlock.cs +++ b/CharacterMap/CharacterMap/Controls/DirectTextBlock.cs @@ -1,222 +1,210 @@ -using CharacterMap.Core; -using Microsoft.Graphics.Canvas; -using Microsoft.Graphics.Canvas.Text; +using Microsoft.Graphics.Canvas.Text; using Microsoft.Graphics.Canvas.UI.Xaml; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Runtime.InteropServices.WindowsRuntime; -using Windows.Foundation; using Windows.UI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Data; -using Windows.UI.Xaml.Documents; -using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; -namespace CharacterMap.Controls +namespace CharacterMap.Controls; + +public sealed class DirectTextBlock : Control { - public sealed class DirectTextBlock : Control + public CanvasFontFace FontFace { - public CanvasFontFace FontFace + get { return (CanvasFontFace)GetValue(FontFaceProperty); } + set { SetValue(FontFaceProperty, value); } + } + + public static readonly DependencyProperty FontFaceProperty = + DependencyProperty.Register(nameof(FontFace), typeof(CanvasFontFace), typeof(DirectTextBlock), new PropertyMetadata(null, (d, e) => { - get { return (CanvasFontFace)GetValue(FontFaceProperty); } - set { SetValue(FontFaceProperty, value); } - } + ((DirectTextBlock)d).Update(); + })); - public static readonly DependencyProperty FontFaceProperty = - DependencyProperty.Register(nameof(FontFace), typeof(CanvasFontFace), typeof(DirectTextBlock), new PropertyMetadata(null, (d, e) => - { - ((DirectTextBlock)d).Update(); - })); + public TypographyFeatureInfo Typography + { + get { return (TypographyFeatureInfo)GetValue(TypographyProperty); } + set { SetValue(TypographyProperty, value); } + } - public TypographyFeatureInfo Typography + public static readonly DependencyProperty TypographyProperty = + DependencyProperty.Register(nameof(Typography), typeof(TypographyFeatureInfo), typeof(DirectTextBlock), new PropertyMetadata(null, (d, e) => { - get { return (TypographyFeatureInfo)GetValue(TypographyProperty); } - set { SetValue(TypographyProperty, value); } - } + ((DirectTextBlock)d).Update(); + })); - public static readonly DependencyProperty TypographyProperty = - DependencyProperty.Register(nameof(Typography), typeof(TypographyFeatureInfo), typeof(DirectTextBlock), new PropertyMetadata(null, (d, e) => - { - ((DirectTextBlock)d).Update(); - })); + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } - public string Text + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register(nameof(Text), typeof(string), typeof(DirectTextBlock), new PropertyMetadata(null, (d, e) => { - get { return (string)GetValue(TextProperty); } - set { SetValue(TextProperty, value); } - } + ((DirectTextBlock)d).Update(); + })); - public static readonly DependencyProperty TextProperty = - DependencyProperty.Register(nameof(Text), typeof(string), typeof(DirectTextBlock), new PropertyMetadata(null, (d,e) => - { - ((DirectTextBlock)d).Update(); - })); + private CanvasControl m_canvas = null; + private CanvasTextLayout m_layout = null; + bool m_isStale = true; + bool m_render = true; - private CanvasControl m_canvas = null; - private CanvasTextLayout m_layout = null; - bool m_isStale = true; - bool m_render = true; + public DirectTextBlock() + { + this.DefaultStyleKey = typeof(DirectTextBlock); + this.Loaded += DirectTextBlock_Loaded; + } - public DirectTextBlock() - { - this.DefaultStyleKey = typeof(DirectTextBlock); - this.Loaded += DirectTextBlock_Loaded; - } + private void DirectTextBlock_Loaded(object sender, RoutedEventArgs e) + { + Update(); + } - private void DirectTextBlock_Loaded(object sender, RoutedEventArgs e) - { - Update(); - } + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); - protected override void OnApplyTemplate() + if (this.GetTemplateChild("TextCanvas") is CanvasControl canvas) { - base.OnApplyTemplate(); + m_canvas = canvas; - if (this.GetTemplateChild("TextCanvas") is CanvasControl canvas) - { - m_canvas = canvas; + m_canvas.Draw -= _canvas_Draw; + m_canvas.CreateResources -= _canvas_CreateResources; - m_canvas.Draw -= _canvas_Draw; - m_canvas.CreateResources -= _canvas_CreateResources; + m_canvas.Draw += _canvas_Draw; + m_canvas.CreateResources += _canvas_CreateResources; + } - m_canvas.Draw += _canvas_Draw; - m_canvas.CreateResources += _canvas_CreateResources; - } + Update(); + } - Update(); - } + private void _canvas_CreateResources(CanvasControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args) + { + Update(); + } - private void _canvas_CreateResources(CanvasControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args) - { - Update(); - } + void Update() + { + m_canvas?.Invalidate(); + } - void Update() + //protected override Size MeasureOverride(Size availableSize) + //{ + // if (FontFace == null || Typography == null || m_canvas == null || !m_canvas.ReadyToDraw) + // return base.MeasureOverride(availableSize); + + + // if (m_layout == null || m_isStale) + // { + // m_isStale = false; + + // if (m_layout != null) + // m_layout?.Dispose(); + + // var text = Text; + + // /* + // FILTER UNSUPPORTED CHARACTERS. + // - This is a bad way of doing this, should be done with + // custom text renderer + // */ + // /* if (UnicodeIndex > 0) + // { + // wchar_t* newData = new wchar_t[2]; + // newData[0] = UnicodeIndex; + // text = ref new Platform::String(newData, 1); + // } + // else + // { + // text = Text; + + // auto data = text->Data(); + // auto l = text->Length(); + // wchar_t* newData = new wchar_t[l]; + // for (int i = 0; i < l; i++) + // { + // wchar_t c = data[i]; + // if (fontFace->HasCharacter(c)) + // newData[i] = c; + // else + // newData[i] = 0; + // } + + // text = ref new Platform::String(newData, l); + // }*/ + + // var format = new CanvasTextFormat(); + // format.FontFamily = FontFamily.Source; + // format.FontSize = (float)FontSize; + // format.FontWeight = FontWeight; + // format.FontStyle = FontStyle; + // format.FontStretch = FontStretch; + // format.Options = CanvasDrawTextOptions.EnableColorFont | CanvasDrawTextOptions.Clip; + + // var typography = new CanvasTypography(); + + // if (Typography.Feature != CanvasTypographyFeatureName.None) + // typography.AddFeature(Typography.Feature, 1); + + // var device = m_canvas.Device; + // var layout = new CanvasTextLayout(device, text, format, (float)availableSize.Width, (float)availableSize.Height); + // layout.SetTypography(0, layout.LineMetrics[0].CharacterCount, typography); + // layout.Options = CanvasDrawTextOptions.EnableColorFont | CanvasDrawTextOptions.Clip; + + // m_layout = layout; + // m_render = true; + // } + + // var minh = Math.Min(m_layout.DrawBounds.Top, m_layout.LayoutBounds.Top); + // var maxh = Math.Max(m_layout.DrawBounds.Bottom, m_layout.LayoutBounds.Bottom); + + // var minw = Math.Min(m_layout.DrawBounds.Left, m_layout.LayoutBounds.Left); + // var maxw = Math.Max(m_layout.DrawBounds.Right, m_layout.LayoutBounds.Right); + + // var targetsize = new Size(Math.Ceiling(maxw - minw), Math.Ceiling(maxh - minh)); + // return targetsize; + //} + + protected override Size ArrangeOverride(Size finalSize) + { + try { - m_canvas?.Invalidate(); - } + return base.ArrangeOverride(finalSize); - //protected override Size MeasureOverride(Size availableSize) - //{ - // if (FontFace == null || Typography == null || m_canvas == null || !m_canvas.ReadyToDraw) - // return base.MeasureOverride(availableSize); - - - // if (m_layout == null || m_isStale) - // { - // m_isStale = false; - - // if (m_layout != null) - // m_layout?.Dispose(); - - // var text = Text; - - // /* - // FILTER UNSUPPORTED CHARACTERS. - // - This is a bad way of doing this, should be done with - // custom text renderer - // */ - // /* if (UnicodeIndex > 0) - // { - // wchar_t* newData = new wchar_t[2]; - // newData[0] = UnicodeIndex; - // text = ref new Platform::String(newData, 1); - // } - // else - // { - // text = Text; - - // auto data = text->Data(); - // auto l = text->Length(); - // wchar_t* newData = new wchar_t[l]; - // for (int i = 0; i < l; i++) - // { - // wchar_t c = data[i]; - // if (fontFace->HasCharacter(c)) - // newData[i] = c; - // else - // newData[i] = 0; - // } - - // text = ref new Platform::String(newData, l); - // }*/ - - // var format = new CanvasTextFormat(); - // format.FontFamily = FontFamily.Source; - // format.FontSize = (float)FontSize; - // format.FontWeight = FontWeight; - // format.FontStyle = FontStyle; - // format.FontStretch = FontStretch; - // format.Options = CanvasDrawTextOptions.EnableColorFont | CanvasDrawTextOptions.Clip; - - // var typography = new CanvasTypography(); - - // if (Typography.Feature != CanvasTypographyFeatureName.None) - // typography.AddFeature(Typography.Feature, 1); - - // var device = m_canvas.Device; - // var layout = new CanvasTextLayout(device, text, format, (float)availableSize.Width, (float)availableSize.Height); - // layout.SetTypography(0, layout.LineMetrics[0].CharacterCount, typography); - // layout.Options = CanvasDrawTextOptions.EnableColorFont | CanvasDrawTextOptions.Clip; - - // m_layout = layout; - // m_render = true; - // } - - // var minh = Math.Min(m_layout.DrawBounds.Top, m_layout.LayoutBounds.Top); - // var maxh = Math.Max(m_layout.DrawBounds.Bottom, m_layout.LayoutBounds.Bottom); - - // var minw = Math.Min(m_layout.DrawBounds.Left, m_layout.LayoutBounds.Left); - // var maxw = Math.Max(m_layout.DrawBounds.Right, m_layout.LayoutBounds.Right); - - // var targetsize = new Size(Math.Ceiling(maxw - minw), Math.Ceiling(maxh - minh)); - // return targetsize; - //} - - protected override Size ArrangeOverride(Size finalSize) + } + finally { - try - { - return base.ArrangeOverride(finalSize); - - } - finally + if (m_render) { - if (m_render) - { - m_canvas?.Invalidate(); - } + m_canvas?.Invalidate(); } } + } - private void _canvas_Draw(CanvasControl sender, CanvasDrawEventArgs args) - { - //if (m_layout == null) - // return; - - //m_render = false; - - //var offset = new Vector2( - // (float)-Math.Min(m_layout.DrawBounds.Left, m_layout.LayoutBounds.Left), - // (float)-Math.Min(m_layout.DrawBounds.Top, m_layout.LayoutBounds.Top)); - - var format = new CanvasTextFormat(); - format.FontFamily = FontFamily.Source; - format.FontSize = (float)FontSize; - format.FontWeight = FontWeight; - format.FontStyle = FontStyle; - format.FontStretch = FontStretch; - format.Options = CanvasDrawTextOptions.EnableColorFont | CanvasDrawTextOptions.Clip; - - args.DrawingSession.DrawText(Text, 0, 0, Colors.Black, format); - //args.DrawingSession.DrawTextLayout( - // m_layout, offset, Colors.Green); - //args.DrawingSession.Flush(); - } + private void _canvas_Draw(CanvasControl sender, CanvasDrawEventArgs args) + { + //if (m_layout == null) + // return; + + //m_render = false; + + //var offset = new Vector2( + // (float)-Math.Min(m_layout.DrawBounds.Left, m_layout.LayoutBounds.Left), + // (float)-Math.Min(m_layout.DrawBounds.Top, m_layout.LayoutBounds.Top)); + + var format = new CanvasTextFormat(); + format.FontFamily = FontFamily.Source; + format.FontSize = (float)FontSize; + format.FontWeight = FontWeight; + format.FontStyle = FontStyle; + format.FontStretch = FontStretch; + format.Options = CanvasDrawTextOptions.EnableColorFont | CanvasDrawTextOptions.Clip; + + args.DrawingSession.DrawText(Text, 0, 0, Colors.Black, format); + //args.DrawingSession.DrawTextLayout( + // m_layout, offset, Colors.Green); + //args.DrawingSession.Flush(); } } diff --git a/CharacterMap/CharacterMap/Controls/ExtendedListView.cs b/CharacterMap/CharacterMap/Controls/ExtendedListView.cs index b3eb9645..27a58310 100644 --- a/CharacterMap/CharacterMap/Controls/ExtendedListView.cs +++ b/CharacterMap/CharacterMap/Controls/ExtendedListView.cs @@ -1,234 +1,228 @@ -using CharacterMap.Core; -using CharacterMap.Helpers; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Collections; using System.Collections.Specialized; -using System.Linq; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Data; -namespace CharacterMap.Controls +namespace CharacterMap.Controls; + +public class ExtendedListViewItem : ListViewItem, IThemeableControl { - public class ExtendedListViewItem : ListViewItem, IThemeableControl + public ThemeHelper _themer; + public ExtendedListViewItem() : base() { - public ThemeHelper _themer; - public ExtendedListViewItem() : base() - { - Properties.SetStyleKey(this, "DefaultThemeListViewItemStyle"); - _themer = new ThemeHelper(this); - } + Properties.SetStyleKey(this, "DefaultThemeListViewItemStyle"); + _themer = new ThemeHelper(this); + } - public void UpdateTheme() - { + public void UpdateTheme() + { + _themer.Update(); + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (this.Style is null) _themer.Update(); - } + } +} + +public class ExtendedListView : ListView +{ + #region Dependency Properties + + public INotifyCollectionChanged BindableSelectedItems + { + get { return (INotifyCollectionChanged)GetValue(BindableSelectedItemsProperty); } + set { SetValue(BindableSelectedItemsProperty, value); } + } - protected override void OnApplyTemplate() + public static readonly DependencyProperty BindableSelectedItemsProperty = + DependencyProperty.Register(nameof(BindableSelectedItems), typeof(INotifyCollectionChanged), typeof(ExtendedListView), new PropertyMetadata(null, (d, e) => { - base.OnApplyTemplate(); + ((ExtendedListView)d).OnSelectedItemsChanged(e); + })); - if (this.Style is null) - _themer.Update(); - } + public bool HasSelection + { + get { return (bool)GetValue(HasSelectionProperty); } + set { SetValue(HasSelectionProperty, value); } } - public class ExtendedListView : ListView + public static readonly DependencyProperty HasSelectionProperty = + DependencyProperty.Register(nameof(HasSelection), typeof(bool), typeof(ExtendedListView), new PropertyMetadata(false)); + + public int SelectionCount { - #region Dependency Properties + get { return (int)GetValue(SelectionCountProperty); } + set { SetValue(SelectionCountProperty, value); } + } - public INotifyCollectionChanged BindableSelectedItems - { - get { return (INotifyCollectionChanged)GetValue(BindableSelectedItemsProperty); } - set { SetValue(BindableSelectedItemsProperty, value); } - } + public static readonly DependencyProperty SelectionCountProperty = + DependencyProperty.Register(nameof(SelectionCount), typeof(int), typeof(ExtendedListView), new PropertyMetadata(0)); - public static readonly DependencyProperty BindableSelectedItemsProperty = - DependencyProperty.Register(nameof(BindableSelectedItems), typeof(INotifyCollectionChanged), typeof(ExtendedListView), new PropertyMetadata(null, (d, e) => - { - ((ExtendedListView)d).OnSelectedItemsChanged(e); - })); + public bool IsSelectionBindingEnabled + { + get { return (bool)GetValue(IsSelectionBindingEnabledProperty); } + set { SetValue(IsSelectionBindingEnabledProperty, value); } + } - public bool HasSelection - { - get { return (bool)GetValue(HasSelectionProperty); } - set { SetValue(HasSelectionProperty, value); } - } + public static readonly DependencyProperty IsSelectionBindingEnabledProperty = + DependencyProperty.Register(nameof(IsSelectionBindingEnabled), typeof(bool), typeof(ExtendedListView), new PropertyMetadata(false)); - public static readonly DependencyProperty HasSelectionProperty = - DependencyProperty.Register(nameof(HasSelection), typeof(bool), typeof(ExtendedListView), new PropertyMetadata(false)); + #endregion - public int SelectionCount - { - get { return (int)GetValue(SelectionCountProperty); } - set { SetValue(SelectionCountProperty, value); } - } - public static readonly DependencyProperty SelectionCountProperty = - DependencyProperty.Register(nameof(SelectionCount), typeof(int), typeof(ExtendedListView), new PropertyMetadata(0)); - public bool IsSelectionBindingEnabled - { - get { return (bool)GetValue(IsSelectionBindingEnabledProperty); } - set { SetValue(IsSelectionBindingEnabledProperty, value); } - } - public static readonly DependencyProperty IsSelectionBindingEnabledProperty = - DependencyProperty.Register(nameof(IsSelectionBindingEnabled), typeof(bool), typeof(ExtendedListView), new PropertyMetadata(false)); + long token = 0; - #endregion + public ExtendedListView() + { + this.Loaded += ExtendedListView_Loaded; + this.Unloaded += ExtendedListView_Unloaded; + } + private void ExtendedListView_Loaded(object sender, RoutedEventArgs e) + { + if (IsSelectionBindingEnabled) + OnAttached(); + } + private void ExtendedListView_Unloaded(object sender, RoutedEventArgs e) + { + if (IsSelectionBindingEnabled) + OnDetaching(); + } + protected override DependencyObject GetContainerForItemOverride() + { + return new ExtendedListViewItem { Style = this.ItemContainerStyle }; + } - long token = 0; + void UpdateSelection() + { + HasSelection = SelectedItems.Count > 0; + SelectionCount = SelectedItems.Count; + } - public ExtendedListView() - { - this.Loaded += ExtendedListView_Loaded; - this.Unloaded += ExtendedListView_Unloaded; - } + public void ClearSelection() + { + SelectedItems.Clear(); + } - private void ExtendedListView_Loaded(object sender, RoutedEventArgs e) + protected void OnAttached() + { + if (BindableSelectedItems == null) { - if (IsSelectionBindingEnabled) - OnAttached(); + BindableSelectedItems = new ObservableCollection(); } - - private void ExtendedListView_Unloaded(object sender, RoutedEventArgs e) + else if (BindableSelectedItems is IEnumerable list) { - if (IsSelectionBindingEnabled) - OnDetaching(); + foreach (var item in list.ToList()) + { + SelectedItems.Add(item); + } } - protected override DependencyObject GetContainerForItemOverride() - { - return new ExtendedListViewItem { Style = this.ItemContainerStyle }; - } + token = RegisterPropertyChangedCallback(ListViewBase.ItemsSourceProperty, ItemsSourceChanged); + + SelectionChanged -= AssociatedObject_SelectionChanged; + SelectionChanged += AssociatedObject_SelectionChanged; - void UpdateSelection() + BindableSelectedItems.CollectionChanged -= SelectedItems_CollectionChanged; + BindableSelectedItems.CollectionChanged += SelectedItems_CollectionChanged; + + UpdateSelection(); + } + + private void OnSelectedItemsChanged(DependencyPropertyChangedEventArgs e) + { + if (e.OldValue is INotifyCollectionChanged c) { - HasSelection = SelectedItems.Count > 0; - SelectionCount = SelectedItems.Count; + c.CollectionChanged -= SelectedItems_CollectionChanged; } - public void ClearSelection() + if (this is null) + return; + + if (e.NewValue is INotifyCollectionChanged n) { - SelectedItems.Clear(); + n.CollectionChanged -= SelectedItems_CollectionChanged; + n.CollectionChanged += SelectedItems_CollectionChanged; } + } - protected void OnAttached() + void ItemsSourceChanged(DependencyObject source, DependencyProperty property) + { + if (SelectedItems is IList list) { - if (BindableSelectedItems == null) - { - BindableSelectedItems = new ObservableCollection(); - } - else if (BindableSelectedItems is IEnumerable list) + foreach (var item in list.ToList()) { - foreach (var item in list.ToList()) - { - SelectedItems.Add(item); - } + list.Remove(item); } + } + } - token = RegisterPropertyChangedCallback(ListViewBase.ItemsSourceProperty, ItemsSourceChanged); - - SelectionChanged -= AssociatedObject_SelectionChanged; - SelectionChanged += AssociatedObject_SelectionChanged; + protected void OnDetaching() + { + UnregisterPropertyChangedCallback(ListViewBase.ItemsSourceProperty, token); + SelectionChanged -= AssociatedObject_SelectionChanged; + if (BindableSelectedItems is not null) BindableSelectedItems.CollectionChanged -= SelectedItems_CollectionChanged; - BindableSelectedItems.CollectionChanged += SelectedItems_CollectionChanged; + } - UpdateSelection(); - } + private void SelectedItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + SelectionChanged -= AssociatedObject_SelectionChanged; - private void OnSelectedItemsChanged(DependencyPropertyChangedEventArgs e) + if (e.Action == NotifyCollectionChangedAction.Add) { - if (e.OldValue is INotifyCollectionChanged c) + foreach (var item in e.NewItems) { - c.CollectionChanged -= SelectedItems_CollectionChanged; - } - - if (this is null) - return; - - if (e.NewValue is INotifyCollectionChanged n) - { - n.CollectionChanged -= SelectedItems_CollectionChanged; - n.CollectionChanged += SelectedItems_CollectionChanged; + if (!SelectedItems.Contains(item)) + SelectedItems.Add(item); } } - - void ItemsSourceChanged(DependencyObject source, DependencyProperty property) + else if (e.Action == NotifyCollectionChangedAction.Remove) { - if (SelectedItems is IList list) + foreach (var item in e.OldItems) { - foreach (var item in list.ToList()) - { - list.Remove(item); - } + if (!SelectedItems.Contains(item)) + SelectedItems.Remove(item); } } - - protected void OnDetaching() + else if (e.Action == NotifyCollectionChangedAction.Reset) { - UnregisterPropertyChangedCallback(ListViewBase.ItemsSourceProperty, token); - SelectionChanged -= AssociatedObject_SelectionChanged; - - if (BindableSelectedItems is not null) - BindableSelectedItems.CollectionChanged -= SelectedItems_CollectionChanged; + DeselectRange(new ItemIndexRange(0, int.MaxValue)); } - private void SelectedItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + UpdateSelection(); + SelectionChanged += AssociatedObject_SelectionChanged; + } + + private void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (BindableSelectedItems is IList list) { - SelectionChanged -= AssociatedObject_SelectionChanged; + BindableSelectedItems.CollectionChanged -= SelectedItems_CollectionChanged; - if (e.Action == NotifyCollectionChangedAction.Add) + foreach (var item in e.RemovedItems) { - foreach (var item in e.NewItems) - { - if (!SelectedItems.Contains(item)) - SelectedItems.Add(item); - } + list.Remove(item); } - else if (e.Action == NotifyCollectionChangedAction.Remove) - { - foreach (var item in e.OldItems) - { - if (!SelectedItems.Contains(item)) - SelectedItems.Remove(item); - } - } - else if (e.Action == NotifyCollectionChangedAction.Reset) - { - DeselectRange(new ItemIndexRange(0, int.MaxValue)); - } - - UpdateSelection(); - SelectionChanged += AssociatedObject_SelectionChanged; - } - private void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - if (BindableSelectedItems is IList list) + foreach (var item in e.AddedItems) { - BindableSelectedItems.CollectionChanged -= SelectedItems_CollectionChanged; - - foreach (var item in e.RemovedItems) - { - list.Remove(item); - } - - foreach (var item in e.AddedItems) - { - list.Add(item); - } - - BindableSelectedItems.CollectionChanged += SelectedItems_CollectionChanged; + list.Add(item); } - UpdateSelection(); + BindableSelectedItems.CollectionChanged += SelectedItems_CollectionChanged; } + + UpdateSelection(); } } diff --git a/CharacterMap/CharacterMap/Controls/ExtendedSplitView.cs b/CharacterMap/CharacterMap/Controls/ExtendedSplitView.cs index 8451bf50..926d500a 100644 --- a/CharacterMap/CharacterMap/Controls/ExtendedSplitView.cs +++ b/CharacterMap/CharacterMap/Controls/ExtendedSplitView.cs @@ -1,59 +1,58 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace CharacterMap.Controls +namespace CharacterMap.Controls; + +public class ExtendedSplitView : SplitView { - public class ExtendedSplitView : SplitView + public bool EnableAnimation { - public bool EnableAnimation - { - get { return (bool)GetValue(EnableAnimationProperty); } - set { SetValue(EnableAnimationProperty, value); } - } - - public static readonly DependencyProperty EnableAnimationProperty = - DependencyProperty.Register(nameof(EnableAnimation), typeof(bool), typeof(ExtendedSplitView), new PropertyMetadata(true, (d, e) => - { - if (d is ExtendedSplitView s) - s.UpdateAnimationStates(); - })); - - public ExtendedSplitView() + get { return (bool)GetValue(EnableAnimationProperty); } + set { SetValue(EnableAnimationProperty, value); } + } + + public static readonly DependencyProperty EnableAnimationProperty = + DependencyProperty.Register(nameof(EnableAnimation), typeof(bool), typeof(ExtendedSplitView), new PropertyMetadata(true, (d, e) => { - this.DefaultStyleKey = typeof(ExtendedSplitView); - this.Loaded += ExtendedSplitView_Loaded; - this.Unloaded += ExtendedSplitView_Unloaded; - } + if (d is ExtendedSplitView s) + s.UpdateAnimationStates(); + })); - FrameworkElement _contentRoot = null; - FrameworkElement _paneRoot = null; + public ExtendedSplitView() + { + this.DefaultStyleKey = typeof(ExtendedSplitView); + this.Loaded += ExtendedSplitView_Loaded; + this.Unloaded += ExtendedSplitView_Unloaded; + } - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); + FrameworkElement _contentRoot = null; + FrameworkElement _paneRoot = null; - _contentRoot = this.GetTemplateChild("ContentRoot") as FrameworkElement; - _paneRoot = this.GetTemplateChild("PaneRoot") as FrameworkElement; + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); - UpdateAnimationStates(); - } + _contentRoot = this.GetTemplateChild("ContentRoot") as FrameworkElement; + _paneRoot = this.GetTemplateChild("PaneRoot") as FrameworkElement; - private void ExtendedSplitView_Loaded(object sender, RoutedEventArgs e) - { - UpdateAnimationStates(); - } - private void ExtendedSplitView_Unloaded(object sender, RoutedEventArgs e) - { - UpdateAnimationStates(false); - } + UpdateAnimationStates(); + } - private void UpdateAnimationStates(bool allow = true) - { - if (_contentRoot is not null) - Core.Properties.SetUseStandardReposition(_contentRoot, allow && EnableAnimation); + private void ExtendedSplitView_Loaded(object sender, RoutedEventArgs e) + { + UpdateAnimationStates(); + } + private void ExtendedSplitView_Unloaded(object sender, RoutedEventArgs e) + { + UpdateAnimationStates(false); + } + + private void UpdateAnimationStates(bool allow = true) + { + if (_contentRoot is not null) + Core.Properties.SetUseStandardReposition(_contentRoot, allow && EnableAnimation); - if (_paneRoot is not null) - Styles.Controls.SetEnableSlideOut(_paneRoot, allow && EnableAnimation); - } + if (_paneRoot is not null) + Styles.Controls.SetEnableSlideOut(_paneRoot, allow && EnableAnimation); } } diff --git a/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs b/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs index 6636fd7f..591301fb 100644 --- a/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs +++ b/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs @@ -2,220 +2,219 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace CharacterMap.Controls +namespace CharacterMap.Controls; + +public sealed partial class FilterFlyout : MenuFlyout { - public sealed partial class FilterFlyout : MenuFlyout + private int _defaultCount = 0; + + public ICommand FilterCommand { - private int _defaultCount = 0; + get { return (ICommand)GetValue(FilterCommandProperty); } + set { SetValue(FilterCommandProperty, value); } + } - public ICommand FilterCommand - { - get { return (ICommand)GetValue(FilterCommandProperty); } - set { SetValue(FilterCommandProperty, value); } - } + public static readonly DependencyProperty FilterCommandProperty = + DependencyProperty.Register(nameof(FilterCommand), typeof(ICommand), typeof(FilterFlyout), new PropertyMetadata(null)); - public static readonly DependencyProperty FilterCommandProperty = - DependencyProperty.Register(nameof(FilterCommand), typeof(ICommand), typeof(FilterFlyout), new PropertyMetadata(null)); + public ICommand CollectionSelectedCommand + { + get { return (ICommand)GetValue(CollectionSelectedCommandProperty); } + set { SetValue(CollectionSelectedCommandProperty, value); } + } - public ICommand CollectionSelectedCommand - { - get { return (ICommand)GetValue(CollectionSelectedCommandProperty); } - set { SetValue(CollectionSelectedCommandProperty, value); } - } + public static readonly DependencyProperty CollectionSelectedCommandProperty = + DependencyProperty.Register(nameof(CollectionSelectedCommand), typeof(ICommand), typeof(FilterFlyout), new PropertyMetadata(null)); - public static readonly DependencyProperty CollectionSelectedCommandProperty = - DependencyProperty.Register(nameof(CollectionSelectedCommand), typeof(ICommand), typeof(FilterFlyout), new PropertyMetadata(null)); + private MenuFlyoutItemBase _variableOption = null; + private MenuFlyoutItemBase _remoteOption = null; + private MenuFlyoutItemBase _appxOption = null; + private MenuFlyoutSeparator _fontSep = null; - private MenuFlyoutItemBase _variableOption = null; - private MenuFlyoutItemBase _remoteOption = null; - private MenuFlyoutItemBase _appxOption = null; - private MenuFlyoutSeparator _fontSep = null; + private MenuFlyoutSubItem _ops = null; - private MenuFlyoutSubItem _ops = null; + public FilterFlyout() + { + this.InitializeComponent(); + Create(); + } - public FilterFlyout() + private void Create() + { + // x:Bind doesn't work here because there's no Loading method on MenuFlyout + // for the code gen to work, so the flyout needs to be created in code behind. + + Style style = ResourceHelper.Get