File: MS\Internal\FontCache\FamilyCollection.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//
//
// Description: FamilyCollection font cache element class is responsible for
// storing the mapping between a folder and font families in it.
//
//
 
using System.Collections;
using System.Globalization;
using System.IO;
using System.Windows;
using System.Windows.Markup;    // for XmlLanguage
using System.Windows.Media;
 
using MS.Win32;
using MS.Internal.FontFace;
using MS.Internal.Shaping;
 
// Since we disable PreSharp warnings in this file, we first need to disable warnings about unknown message numbers and unknown pragmas.
#pragma warning disable 1634, 1691
 
namespace MS.Internal.FontCache
{
    /// <summary>
    /// FamilyCollection font cache element class is responsible for
    /// storing the mapping between a folder and font families in it
    /// </summary>
    internal class FamilyCollection
    {
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        private Text.TextInterface.FontCollection _fontCollection;
        private Uri                               _folderUri;
        private List<CompositeFontFamily>         _userCompositeFonts;
        private static object                     _staticLock = new object();
 
        #endregion Private Fields
 
        internal static string SxSFontsResourcePrefix { get; } = $"/{Path.GetFileNameWithoutExtension(ExternDll.PresentationCore)};component/fonts/";
 
        private static List<CompositeFontFamily> GetCompositeFontList(FontSourceCollection fontSourceCollection)
        {
            List<CompositeFontFamily> compositeFonts = new List<CompositeFontFamily>();
 
            foreach (FontSource fontSource in fontSourceCollection)
            {
                if (fontSource.IsComposite)
                {
                    CompositeFontInfo fontInfo = CompositeFontParser.LoadXml(fontSource.GetStream());
                    CompositeFontFamily compositeFamily = new CompositeFontFamily(fontInfo);
                    compositeFonts.Add(compositeFamily);
                }
            }
 
            return compositeFonts;
        }
 
        private bool UseSystemFonts
        {
            get
            {
                return (_fontCollection == DWriteFactory.SystemFontCollection);
            }
        }
 
        private IList<CompositeFontFamily> UserCompositeFonts
        {
            get
            {
                if (_userCompositeFonts == null)
                {
                    _userCompositeFonts = GetCompositeFontList(new FontSourceCollection(_folderUri, true));
                }
                return _userCompositeFonts;
            }
        }
 
        private static class LegacyArabicFonts
        {
            private static bool              _usePrivateFontCollectionIsInitialized = false;
            private static object            _staticLock = new object();
            private static bool              _usePrivateFontCollectionForLegacyArabicFonts;
            private static readonly string[] _legacyArabicFonts;
            private static Text.TextInterface.FontCollection _legacyArabicFontCollection;
 
 
            static LegacyArabicFonts()
            {
                _legacyArabicFonts = new string[] { "Traditional Arabic",
                                                    "Andalus",
                                                    "Simplified Arabic",
                                                    "Simplified Arabic Fixed" };
            }
 
            internal static Text.TextInterface.FontCollection LegacyArabicFontCollection
            {
                get
                {
                    if (_legacyArabicFontCollection == null)
                    {
                        lock (_staticLock)
                        {
                            if (_legacyArabicFontCollection == null)
                            {
                                Uri criticalSxSFontsLocation = new Uri(FamilyCollection.SxSFontsResourcePrefix);
                                _legacyArabicFontCollection = DWriteFactory.GetFontCollectionFromFolder(criticalSxSFontsLocation);
                            }
                        }
                    }
                    return _legacyArabicFontCollection;
                }
            }
 
            /// <summary>
            /// Checks if a given family name is one of the legacy Arabic fonts.
            /// </summary>
            /// <param name="familyName">The family name without any face info.</param>
            internal static bool IsLegacyArabicFont(string familyName)
            {
                for (int i = 0; i < _legacyArabicFonts.Length; ++i)
                {
                    if (string.Equals(familyName, _legacyArabicFonts[i], StringComparison.OrdinalIgnoreCase))
                    {
                        return true;
                    }
                }
                return false;
            }
 
            /// <summary>
            /// We will use the private font collection to load some Arabic fonts
            /// only on OSes lower than Win7, since the fonts on these OSes are legacy fonts
            /// and the shaping engines that DWrite uses does not handle them properly.
            /// </summary>
            internal static bool UsePrivateFontCollectionForLegacyArabicFonts
            {
                get
                {
                    if (!_usePrivateFontCollectionIsInitialized)
                    {
                        lock (_staticLock)
                        {
                            if (!_usePrivateFontCollectionIsInitialized)
                            {
                                try
                                {
                                    OperatingSystem osInfo = Environment.OSVersion;
                                    // The version of Win7 is 6.1
                                    _usePrivateFontCollectionForLegacyArabicFonts = (osInfo.Version.Major < 6)
                                                                                 || (osInfo.Version.Major == 6
                                                                                     &&
                                                                                     osInfo.Version.Minor == 0);
                                }
                                //Environment.OSVersion was unable to obtain the system version.
                                //-or- 
                                //The obtained platform identifier is not a member of PlatformID.
                                catch (InvalidOperationException)
                                {
                                    // We do not want to bubble this exception up to the user since the user
                                    // has nothing to do about it.
                                    // Instead we will silently fallback to using the private fonts collection 
                                    // so that we guarantee that the text shows properly.
                                    _usePrivateFontCollectionForLegacyArabicFonts = true;
                                }
 
                                _usePrivateFontCollectionIsInitialized = true;
                            }
                        }
                    }
                    return _usePrivateFontCollectionForLegacyArabicFonts;
                }
            }
        }
 
        /// <summary>
        /// This class encapsulates the 4 system composite fonts.
        /// </summary>
        /// <remarks>
        /// This class has direct knowledge about the 4 composite fonts that ship with WPF.
        /// </remarks>
        private static class SystemCompositeFonts
        {
            /// The number of the system composite fonts that ship with WPF is 4.
            internal const int NumOfSystemCompositeFonts = 4;
 
            private static object                _systemCompositeFontsLock = new object();
            private static readonly string[]     _systemCompositeFontsNames;
            private static readonly string[]     _systemCompositeFontsFileNames;
            private static CompositeFontFamily[] _systemCompositeFonts;
 
            static SystemCompositeFonts()
            {
                _systemCompositeFontsNames     = new string[] { "Global User Interface", "Global Monospace", "Global Sans Serif", "Global Serif" };
                _systemCompositeFontsFileNames = new string[] { "GlobalUserInterface", "GlobalMonospace", "GlobalSansSerif", "GlobalSerif" };
                _systemCompositeFonts = new CompositeFontFamily[NumOfSystemCompositeFonts];
            }
 
            /// <summary>
            /// Returns the composite font to be used to fallback to a different font
            /// if one of the legacy Arabic fonts is specifed.
            /// </summary>
            internal static CompositeFontFamily GetFallbackFontForArabicLegacyFonts()
            {
                return GetCompositeFontFamilyAtIndex(1);
            }
 
            /// <summary>
            /// This method returns the composite font family (or null if not found) given its name
            /// </summary>
            internal static CompositeFontFamily FindFamily(string familyName)
            {
                int index = GetIndexOfFamily(familyName);
                if (index >= 0)
                {
                    return GetCompositeFontFamilyAtIndex(index);
                }
                return null;
            }
 
            /// <summary>
            /// This method returns the composite font with the given index after
            /// lazily allocating it if it has not been already allocated.
            /// </summary>
            internal static CompositeFontFamily GetCompositeFontFamilyAtIndex(int index)
            {
                if (_systemCompositeFonts[index] == null)
                {
                    lock (_systemCompositeFontsLock)
                    {
                        if (_systemCompositeFonts[index] == null)
                        {
                            FontSource fontSource = new FontSource(new Uri(Path.Combine(FamilyCollection.SxSFontsResourcePrefix, _systemCompositeFontsFileNames[index] + Util.CompositeFontExtension), UriKind.RelativeOrAbsolute),
                                                                   isComposite:true,
                                                                   isInternalCompositeFont:true);
 
                            CompositeFontInfo fontInfo = CompositeFontParser.LoadXml(fontSource.GetStream());
                            _systemCompositeFonts[index] = new CompositeFontFamily(fontInfo);
                        }
                    }
                }
                return _systemCompositeFonts[index];
            }
 
            /// <summary>
            /// This method returns the index of the system composite font in _systemCompositeFontsNames.
            /// </summary>
            private static int GetIndexOfFamily(string familyName)
            {
                for (int i = 0; i < _systemCompositeFontsNames.Length; ++i)
                {
                    if (string.Equals(_systemCompositeFontsNames[i], familyName, StringComparison.OrdinalIgnoreCase))
                    {
                        return i;
                    }
                }
                return -1;
            }
        }
 
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
        /// <summary>
        /// Creates a font family collection cache element from a canonical font family reference.
        /// </summary>
        /// <param name="folderUri">Absolute Uri of a folder</param>
        /// <param name="fontCollection">Collection of fonts loaded from the folderUri location</param>
        private FamilyCollection(Uri folderUri, MS.Internal.Text.TextInterface.FontCollection fontCollection)
        {
            _folderUri = folderUri;
            _fontCollection = fontCollection;
        }
 
        /// <summary>
        /// Creates a font family collection cache element from a canonical font family reference.
        /// </summary>
        /// <param name="folderUri">Absolute Uri of a folder</param>
        internal static FamilyCollection FromUri(Uri folderUri)
        {
            return new FamilyCollection(folderUri, DWriteFactory.GetFontCollectionFromFolder(folderUri));
        }
 
        /// <summary>
        /// Creates a font family collection cache element from a canonical font family reference.
        /// </summary>
        /// <param name="folderUri">Absolute Uri to the Windows Fonts folder or a file in the Windows Fonts folder.</param>
        internal static FamilyCollection FromWindowsFonts(Uri folderUri)
        {
            return new FamilyCollection(folderUri, DWriteFactory.SystemFontCollection);
        }
 
        #endregion Constructors
 
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal methods
 
        /// <summary>
        /// This method looks up a certain family in this collection given its name.
        /// If the name was for a specific font face then this method will return its
        /// style, weight and stretch information.
        /// </summary>
        /// <param name="familyName">The name of the family to look for.</param>
        /// <param name="fontStyle">The style if the font face in case family name contained style info.</param>
        /// <param name="fontWeight">The weight if the font face in case family name contained style info.</param>
        /// <param name="fontStretch">The stretch if the font face in case family name contained style info.</param>
        /// <returns>The font family if found.</returns>
        internal IFontFamily LookupFamily(
            string familyName,
            ref FontStyle fontStyle,
            ref FontWeight fontWeight,
            ref FontStretch fontStretch
            )
        {
            if (familyName == null || familyName.Length == 0)
                return null;
 
            familyName = familyName.Trim();
 
            // If we are referencing fonts from the system fonts, then it is cheap to lookup the 4 composite fonts
            // that ship with WPF. Also, it happens often that familyName is "Global User Interface".
            // So in this case we preceed looking into SystemComposite Fonts.
            if (UseSystemFonts)
            {
                CompositeFontFamily compositeFamily = SystemCompositeFonts.FindFamily(familyName);
                if (compositeFamily != null)
                {
                    return compositeFamily;
                }
            }
 
            Text.TextInterface.FontFamily fontFamilyDWrite = _fontCollection[familyName];
 
            // A font family was not found in DWrite's font collection.
            if (fontFamilyDWrite == null)
            {
                // Having user defined composite fonts is not very common. So we defer looking into them to looking DWrite 
                // (which is opposite to what we do for system fonts).
                if (!UseSystemFonts)
                {
                    // The family name was not found in DWrite's font collection. It may possibly be the name of a composite font
                    // since DWrite does not recognize composite fonts.
                    CompositeFontFamily compositeFamily = LookUpUserCompositeFamily(familyName);
                    if (compositeFamily != null)
                    {
                        return compositeFamily;
                    }
                }
 
                // The family name cannot be found. This may possibly be because the family name contains styling info.
                // For example, "Arial Bold"
                // We will strip off the styling info (one word at a time from the end) and try to find the family name.
                int indexOfSpace = -1;
                string originalFamilyName = familyName;
 
                // Start removing off strings from the end hoping they are
                // style info so as to get down to the family name.
                do
                {
                    indexOfSpace = familyName.LastIndexOf(' ');
                    if (indexOfSpace < 0)
                    {
                        break;
                    }
                    else
                    {
                        // store the stripped off style names to look for the specific face later.
                        familyName = familyName.Substring(0, indexOfSpace);
                    }
 
                    fontFamilyDWrite = _fontCollection[familyName];
                } while (fontFamilyDWrite == null);
 
 
                if (fontFamilyDWrite == null)
                {
                    return null;
                }
 
                // If there was styling information.
                if (familyName.Length != originalFamilyName.Length)
                {
                    // To obtain the face name, we remove the family name and the next char (A space) from the original family name.
                    int faceNameIndex = familyName.Length + 1;
                    ReadOnlySpan<char> faceName = originalFamilyName.AsSpan(faceNameIndex);
                    Text.TextInterface.Font font = GetFontFromFamily(fontFamilyDWrite, faceName);
 
                    if (font != null)
                    {
                        fontStyle = new FontStyle((int)font.Style);
                        fontWeight = new FontWeight((int)font.Weight);
                        fontStretch = new FontStretch((int)font.Stretch);
                    }
                }
            }
 
            if (UseSystemFonts
                && LegacyArabicFonts.UsePrivateFontCollectionForLegacyArabicFonts
                // familyName will hold the family name without any face info.
                && LegacyArabicFonts.IsLegacyArabicFont(familyName))
            {
                fontFamilyDWrite = LegacyArabicFonts.LegacyArabicFontCollection[familyName];
                if (fontFamilyDWrite == null)
                {
                    return SystemCompositeFonts.GetFallbackFontForArabicLegacyFonts();
                }
            }
 
            return new PhysicalFontFamily(fontFamilyDWrite);
        }
 
        private CompositeFontFamily LookUpUserCompositeFamily(string familyName)
        {
            if (UserCompositeFonts != null)
            {
                foreach (CompositeFontFamily compositeFamily in UserCompositeFonts)
                {
                    foreach (KeyValuePair<XmlLanguage, string> localizedFamilyName in compositeFamily.FamilyNames)
                    {
                        if (string.Equals(localizedFamilyName.Value, familyName, StringComparison.OrdinalIgnoreCase))
                        {
                            return compositeFamily;
                        }
                    }
                }
            }
 
            return null;
        }
 
        /// <summary>
        /// Given a DWrite font family, look into it for the given face.
        /// </summary>
        /// <param name="fontFamily">The font family to look in.</param>
        /// <param name="faceName">The face to look for.</param>
        /// <returns>The font face if found and null if nothing was found.</returns>
        private static Text.TextInterface.Font GetFontFromFamily(Text.TextInterface.FontFamily fontFamily, ReadOnlySpan<char> faceName)
        {
            // The search that DWrite supports is a linear search.
            // Look at every font face.
            foreach (Text.TextInterface.Font font in fontFamily)
            {
                // and at every locale name this font face has.
                foreach (KeyValuePair<CultureInfo, string> name in font.FaceNames)
                {
                    if (faceName.Equals(name.Value, StringComparison.OrdinalIgnoreCase))
                    {
                        return font;
                    }
                }
            }
 
            // This dictionary is used to store the faces (indexed by their names).
            // This dictionary will be used in case the exact string "faceName" was not found,
            // thus we will start again removing words (separated by ' ') from its end and looking
            // for the resulting faceName in that dictionary. So this dictionary is 
            // used to speed the search.
            Dictionary<string, Text.TextInterface.Font> faces = new Dictionary<string, Text.TextInterface.Font>(StringComparer.OrdinalIgnoreCase);
 
            //We could have merged this loop with the one above. However this will degrade the performance 
            //of the scenario where the user entered  a correct face name (which is the common scenario).
            //Thus we adopt a pay for play approach, meaning that only whenever the face name does not
            //exactly correspond to an actual face name we will incure this overhead.
            foreach (Text.TextInterface.Font font in fontFamily)
            {
                foreach (KeyValuePair<CultureInfo, string> name in font.FaceNames)
                {
                    faces.TryAdd(name.Value, font);
                }
            }
 
            // An exact match was not found and so we will start looking for the best match.
            Text.TextInterface.Font matchingFont = null;
            int indexOfSpace = faceName.LastIndexOf(' ');
 
            while (indexOfSpace > 0)
            {
                faceName = faceName.Slice(0, indexOfSpace);
                if (faces.TryGetValue(faceName.ToString(), out matchingFont))
                {
                    return matchingFont;
                }
 
                indexOfSpace = faceName.LastIndexOf(' ');
            }
 
            // No match was found.
            return null;
        }
 
        private struct FamilyEnumerator : IEnumerator<Text.TextInterface.FontFamily>, IEnumerable<Text.TextInterface.FontFamily>
        {
            private uint _familyCount;
            private Text.TextInterface.FontCollection _fontCollection;
            private bool _firstEnumeration;
            private uint _currentFamily;
 
            internal FamilyEnumerator(Text.TextInterface.FontCollection fontCollection)
            {
                _fontCollection = fontCollection;
                _currentFamily = 0;
                _firstEnumeration = true;
                _familyCount = fontCollection.FamilyCount;
}
 
            #region IEnumerator<Text.TextInterface.FontFamily> Members
 
            public bool MoveNext()
            {
                if (_firstEnumeration)
                {
                    _firstEnumeration = false;
                }
                else
                {
                    ++_currentFamily;
                }
                if (_currentFamily >= _familyCount)
                {
                    // prevent cycling
                    _currentFamily = _familyCount;
                    return false;
                }
                return true;
            }
 
            Text.TextInterface.FontFamily IEnumerator<Text.TextInterface.FontFamily>.Current
            {
                get
                {
                    if (_currentFamily < 0 || _currentFamily >= _familyCount)
                    {
                        throw new InvalidOperationException();
                    }
 
                    return _fontCollection[_currentFamily];
                }
            }
 
            #endregion
 
            #region IEnumerator Members
 
            object IEnumerator.Current
            {
                get
                {
                    return ((IEnumerator<Text.TextInterface.FontFamily>)this).Current;
                }
            }
 
            public void Reset()
            {
                _currentFamily = 0;
                _firstEnumeration = true;
            }
 
            #endregion
 
 
            #region IDisposable Members
 
            public void Dispose() { }
 
            #endregion
 
            #region IEnumerable<Text.TextInterface.FontFamily> Members
 
            IEnumerator<Text.TextInterface.FontFamily> IEnumerable<Text.TextInterface.FontFamily>.GetEnumerator()
            {
                return this as IEnumerator<Text.TextInterface.FontFamily>;
            }
 
            #endregion
 
            #region IEnumerable Members
 
            IEnumerator IEnumerable.GetEnumerator()
            {
                return ((IEnumerable<Text.TextInterface.FontFamily>)this).GetEnumerator();
            }
 
            #endregion
        }
 
        private IEnumerable<Text.TextInterface.FontFamily> GetPhysicalFontFamilies()
        {
            return new FamilyEnumerator(this._fontCollection);
        }
 
        internal FontFamily[] GetFontFamilies(Uri fontFamilyBaseUri, string fontFamilyLocationReference)
        {
            FontFamily[] fontFamilyList = new FontFamily[FamilyCount];
            int i = 0;
            foreach (MS.Internal.Text.TextInterface.FontFamily family in GetPhysicalFontFamilies())
            {
                string fontFamilyReference = Util.ConvertFamilyNameAndLocationToFontFamilyReference(
                    family.OrdinalName,
                    fontFamilyLocationReference
                    );
 
                string friendlyName = Util.ConvertFontFamilyReferenceToFriendlyName(fontFamilyReference);
 
                fontFamilyList[i++] = new FontFamily(fontFamilyBaseUri, friendlyName);
            }
 
            FontFamily fontFamily;
            if (UseSystemFonts)
            {
                for (int j = 0; j < SystemCompositeFonts.NumOfSystemCompositeFonts; ++j)
                {
                    fontFamily = CreateFontFamily(SystemCompositeFonts.GetCompositeFontFamilyAtIndex(j), fontFamilyBaseUri, fontFamilyLocationReference);
                    if (fontFamily != null)
                    {
                        fontFamilyList[i++] = fontFamily;
                    }
                }
            }
            else
            {
                foreach (CompositeFontFamily compositeFontFamily in UserCompositeFonts)
                {
                    fontFamily = CreateFontFamily(compositeFontFamily, fontFamilyBaseUri, fontFamilyLocationReference);
                    if (fontFamily != null)
                    {
                        fontFamilyList[i++] = fontFamily;
                    }
                }
            }
 
            Debug.Assert(i == FamilyCount);
 
            return fontFamilyList;
        }
 
        private FontFamily CreateFontFamily(CompositeFontFamily compositeFontFamily, Uri fontFamilyBaseUri, string fontFamilyLocationReference)
        {
            IFontFamily fontFamily = (IFontFamily)compositeFontFamily;
            IEnumerator<string> familyNames = fontFamily.Names.Values.GetEnumerator();
            if (familyNames.MoveNext())
            {
                string ordinalName = familyNames.Current;
                string fontFamilyReference = Util.ConvertFamilyNameAndLocationToFontFamilyReference(
                ordinalName,
                fontFamilyLocationReference
                );
 
                string friendlyName = Util.ConvertFontFamilyReferenceToFriendlyName(fontFamilyReference);
 
                return new FontFamily(fontFamilyBaseUri, friendlyName);
            }
            return null;
        }
 
        internal uint FamilyCount
        {
            get
            {
                return _fontCollection.FamilyCount + (UseSystemFonts ? SystemCompositeFonts.NumOfSystemCompositeFonts : checked((uint)UserCompositeFonts.Count));
            }
        }
 
        #endregion Internal methods
    }
}