File: System\Windows\Media\FontFamily.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.
 
using System.IO;
using System.Windows.Markup;
using System.ComponentModel;
using MS.Internal;
using MS.Internal.FontCache;
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 System.Windows.Media
{
    /// <summary>
    /// Represents a family of related fonts. Fonts in a FontFamily differ only in style,
    /// weight, or stretch.
    /// </summary>
    [TypeConverter(typeof(FontFamilyConverter))]
    [ValueSerializer(typeof(FontFamilyValueSerializer))]
    [Localizability(LocalizationCategory.Font)]
    public class FontFamily
    {
        /// <summary>
        /// Family name originally passed to by user and information derived from it.
        /// </summary>
        private FontFamilyIdentifier _familyIdentifier;
 
        /// <summary>
        /// The first valid font family. If no valid font family can be resolved from 
        /// the given name, this will point to a NullFontFamily object.
        /// </summary>
        private IFontFamily _firstFontFamily;
 
        /// <summary>
        /// Null font is the font that has metrics but logically does not support any Unicode codepoint
        /// so whatever text we throw at it would result in being mapped to missing glyph.
        /// </summary>
        internal static readonly CanonicalFontFamilyReference NullFontFamilyCanonicalName = CanonicalFontFamilyReference.Create(null, "#ARIAL");
 
        internal const string GlobalUI = "#GLOBAL USER INTERFACE";
 
        internal static FontFamily FontFamilyGlobalUI = new FontFamily(GlobalUI);
 
        private static volatile FamilyCollection _defaultFamilyCollection = PreCreateDefaultFamilyCollection();
 
        private static FontFamilyMapCollection _emptyFamilyMaps = null;
 
 
        /// <summary>
        /// Constructs FontFamily from a string.
        /// </summary>
        /// <param name="familyName">Specifies one or more comma-separated family names, each
        /// of which may be either a regular family name string (e.g., "Arial") or a URI
        /// (e.g., "file:///c:/windows/fonts/#Arial").</param>
        public FontFamily(string familyName) : this(null, familyName)
        {}
 
        /// <summary>
        /// Constructs FontFamily from a string and an optional base URI.
        /// </summary>
        /// <param name="baseUri">Specifies the base URI used to resolve family names, typically 
        /// the URI of the document or element that refers to the font family. Can be null.</param>
        /// <param name="familyName">Specifies one or more comma-separated family names, each
        /// of which may be either a regular family name string (e.g., "Arial") or a URI
        /// (e.g., "file:///c:/windows/fonts/#Arial").</param>
        public FontFamily(Uri baseUri, string familyName)
        {
            ArgumentNullException.ThrowIfNull(familyName);
 
            if (baseUri != null && !baseUri.IsAbsoluteUri)
                throw new ArgumentException(SR.UriNotAbsolute, "baseUri");
 
            _familyIdentifier = new FontFamilyIdentifier(familyName, baseUri);
        }
 
        internal FontFamily(FontFamilyIdentifier familyIdentifier)
        {
            _familyIdentifier = familyIdentifier;
        }
 
        /// <summary>
        /// Construct an anonymous font family, i.e., a composite font that is created
        /// programatically instead of referenced by name or URI.
        /// </summary>
        public FontFamily()
        {
            _familyIdentifier = new FontFamilyIdentifier(null, null);
            _firstFontFamily = new CompositeFontFamily();
        }
 
        /// <summary>
        /// Collection of culture-dependant family names.
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public LanguageSpecificStringDictionary FamilyNames
        {
            get
            {
                CompositeFontFamily compositeFont = FirstFontFamily as CompositeFontFamily;
                if (compositeFont != null)
                {
                    // Return the read/write dictionary of family names.
                    return compositeFont.FamilyNames;
                }
                else
                {
                    // Return a wrapper for the cached family's read-only dictionary.
                    return new LanguageSpecificStringDictionary(FirstFontFamily.Names);
                }
            }
        }
 
        /// <summary>
        /// List of FamilyTypeface objects.
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public FamilyTypefaceCollection FamilyTypefaces
        {
            get
            {
                CompositeFontFamily compositeFont = FirstFontFamily as CompositeFontFamily;
                if (compositeFont != null)
                {
                    // Return the read/write list of typefaces for the font.
                    return compositeFont.FamilyTypefaces;
                }
                else
                {
                    // Return a wrapper for the read-only collection of typefaces.
                    return new FamilyTypefaceCollection(FirstFontFamily.GetTypefaces(_familyIdentifier));
                }
            }
        }
 
        /// <summary>
        /// Collection of FontFamilyMap objects for an anonymous font family. For named font
        /// families, this property returns an empty, read-only list.
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public FontFamilyMapCollection FamilyMaps
        {
            get
            {
                CompositeFontFamily compositeFont = FirstFontFamily as CompositeFontFamily;
                if (compositeFont != null)
                {
                    // Read the read/write list of family maps for the font.
                    return compositeFont.FamilyMaps;
                }
                else
                {
                    // Return an empty, read-only collection of FamilyMaps.
                    if (_emptyFamilyMaps == null)
                    {
                        _emptyFamilyMaps = new FontFamilyMapCollection(null);
                    }
                    return _emptyFamilyMaps;
                }
            }
        }
 
        /// <summary>
        /// Family names and/or URIs used to construct the font family.
        /// </summary>
        public string Source
        {
            get { return _familyIdentifier.Source; }
        }
 
        /// <summary>
        /// Base URI used to resolve family names, typically the URI of the document or element 
        /// that refers to the font family.
        /// </summary>
        /// <remarks>
        /// Family names are interpreted first relative to the base URI (if not null) and then
        /// relative to the default folder for installed fonts.
        /// </remarks>
        public Uri BaseUri
        {
            get { return _familyIdentifier.BaseUri; }
        }
 
        /// <summary>
        /// Return Source if there is one or String.Empty for unnamed
        /// font family.
        /// </summary>
        public override string ToString()
        {
            string source = _familyIdentifier.Source;
            return source != null ? source : string.Empty;
        }
 
 
        internal FontFamilyIdentifier FamilyIdentifier
        {
            get { return _familyIdentifier; }
        }
 
 
        /// <summary>
        /// Distance from character cell top to English baseline relative to em size. 
        /// </summary>
        public double Baseline
        {
            get
            {
                return FirstFontFamily.BaselineDesign;
            }
 
            set
            {
                VerifyMutable().SetBaseline(value);
            }
        }
 
 
        /// <summary>
        /// Recommended baseline-to-baseline distance for the text in this font relative to em size.
        /// </summary>
        public double LineSpacing
        {
            get
            {
                return FirstFontFamily.LineSpacingDesign;
            }
 
            set
            {
                VerifyMutable().SetLineSpacing(value);
            }
        }
 
        internal double GetLineSpacingForDisplayMode(double emSize, double pixelsPerDip)
        {
            return FirstFontFamily.LineSpacing(emSize, 1, pixelsPerDip, TextFormattingMode.Display);
        }
 
        /// <summary>
        /// Font families from the default system font location.
        /// </summary>
        /// <value>Collection of FontFamly objects from the default system font location.</value>
        [CLSCompliant(false)]
        public ICollection<Typeface> GetTypefaces()
        {
            return FirstFontFamily.GetTypefaces(_familyIdentifier);
        }
 
 
        /// <summary>
        /// Create correspondent hash code for the object
        /// </summary>
        /// <returns>object hash code</returns>
        public override int GetHashCode()
        {
            if (_familyIdentifier.Source != null)
            {
                // named font family: hash based on canonical name
                return _familyIdentifier.GetHashCode();
            }
            else
            {
                // unnamed family: hash is based on object identity
                return base.GetHashCode();
            }
        }
 
 
        /// <summary>
        /// Equality check
        /// </summary>
        public override bool Equals(object o)
        {
            FontFamily f = o as FontFamily;
            if (f == null)
            {
                // different types or o == null
                return false;
            }
            else if (_familyIdentifier.Source != null)
            {
                // named font family; compare canonical names
                return _familyIdentifier.Equals(f._familyIdentifier);
            }
            else
            {
                // unnamed font families are equal only if they're the same instance
                return base.Equals(o);
            }
        }
 
 
        /// <summary>
        /// Verifies that the FontFamily can be changed and returns a CompositeFontFamily
        /// </summary>
        private CompositeFontFamily VerifyMutable()
        {
            CompositeFontFamily mutableFamily = _firstFontFamily as CompositeFontFamily;
 
            if (mutableFamily == null)
            {
                throw new NotSupportedException(SR.FontFamily_ReadOnly);
            }
 
            return mutableFamily;
        }
     
 
        /// <summary>
        /// First font family
        /// </summary>
        internal IFontFamily FirstFontFamily
        {
            get
            {
                IFontFamily family = _firstFontFamily;
 
                if (family == null)
                {
                    // Call Canonicalize() directly so it won't just be called on the boxed object.
                    _familyIdentifier.Canonicalize();
 
                    // Look up first font family from cache. If not found, construct a new one.
                    family = TypefaceMetricsCache.ReadonlyLookup(FamilyIdentifier) as IFontFamily;
 
                    if (family == null)
                    {
                        FontStyle style     = FontStyles.Normal;
                        FontWeight weight   = FontWeights.Normal;
                        FontStretch stretch = FontStretches.Normal;
                        family = FindFirstFontFamilyAndFace(ref style, ref weight, ref stretch);
 
                        if (family == null)
                        {
                            // fall back to null font
                            family = LookupFontFamily(NullFontFamilyCanonicalName);
                            Invariant.Assert(family != null);
                        }
 
                        TypefaceMetricsCache.Add(FamilyIdentifier, family);
                    }
 
                    _firstFontFamily = family;
                }
 
                return family;               
            }
        }
        
 
        #region Resolving family name to font family
 
        /// <summary>
        /// Scan the friendly name string finding the first valid font family
        /// </summary>
        internal static IFontFamily FindFontFamilyFromFriendlyNameList(string friendlyNameList)
        {
            IFontFamily firstFontFamily = null;
 
            // Split limits the number of tokens in a family name.
            FontFamilyIdentifier identifier = new FontFamilyIdentifier(friendlyNameList, null);
            for (int i = 0, c = identifier.Count; firstFontFamily == null && i < c; i++)
            {
                firstFontFamily = LookupFontFamily(identifier[i]);
            }
 
            if (firstFontFamily == null)
            {
                // cannot find first font family, assume null font for first font family
                firstFontFamily = LookupFontFamily(NullFontFamilyCanonicalName);
 
                // null font family should always exist
                Invariant.Assert(firstFontFamily != null);
}
 
            return firstFontFamily;
        }
 
 
        /// <summary>
        /// Create font family from canonical family and ensure at least a 
        /// fallback family is created if the specified name cannot be resolved.
        /// </summary>
        internal static IFontFamily SafeLookupFontFamily(
            CanonicalFontFamilyReference canonicalName,
            out bool                     nullFont
            )
        {
            nullFont = false;
 
            IFontFamily fontFamily = LookupFontFamily(canonicalName);
 
            if(fontFamily == null)
            {
                nullFont = true;
                fontFamily = LookupFontFamily(NullFontFamilyCanonicalName);
                Invariant.Assert(fontFamily != null, "Unable to create null font family");
            }
            
            return fontFamily;
        }
 
 
        /// <summary>
        /// Look up font family from canonical name
        /// </summary>
        /// <param name="canonicalName">font family canonical name</param>
        internal static IFontFamily LookupFontFamily(CanonicalFontFamilyReference canonicalName)
        {
            FontStyle style     = FontStyles.Normal;
            FontWeight weight   = FontWeights.Normal;
            FontStretch stretch = FontStretches.Normal;
 
            return LookupFontFamilyAndFace(canonicalName, ref style, ref weight, ref stretch);
        }
 
        #endregion
 
 
        #region Resolving face name into font family and implied face
 
        /// <summary>
        /// Precreates family collection for Windows Fonts folder, so that we don't have to repeat lookup
        /// every time for it.
        /// </summary>
        /// <returns></returns>
        private static FamilyCollection PreCreateDefaultFamilyCollection()
        {
            FamilyCollection familyCollection = FamilyCollection.FromWindowsFonts(Util.WindowsFontsUriObject);
            return familyCollection;
        }
 
 
        /// <summary>
        /// Find the first valid IFontFamily, if any, for this FontFamily and sets the style, weight,
        /// and stretch to valies implied by the font family (e.g., "Arial Bold" implies FontWeight.Bold).
        /// </summary>
        internal IFontFamily FindFirstFontFamilyAndFace(
            ref FontStyle   style,
            ref FontWeight  weight,
            ref FontStretch stretch
            )
        {
            if (_familyIdentifier.Source == null)
            {
                Invariant.Assert(_firstFontFamily != null, "Unnamed FontFamily should have a non-null first font family");
                return _firstFontFamily;
            }
 
            IFontFamily firstFontFamily = null;
            
            _familyIdentifier.Canonicalize();
 
            for (int i = 0, c = _familyIdentifier.Count; firstFontFamily == null && i < c; ++i)
            {
                firstFontFamily = LookupFontFamilyAndFace(
                    _familyIdentifier[i],
                    ref style,
                    ref weight,
                    ref stretch);
            }
 
            return firstFontFamily;
        }
 
 
        /// <summary>
        /// Lookup font family from canonical name.
        /// </summary>
        /// <param name="canonicalFamilyReference">font face canonical name</param>
        /// <param name="style">FontStyle implied by the font family.</param>
        /// <param name="weight">FontWeight implied by the font family.</param>
        /// <param name="stretch">FontStretch implied by the font family.</param>
        /// <returns>The font family object.</returns>
        internal static IFontFamily LookupFontFamilyAndFace(
            CanonicalFontFamilyReference canonicalFamilyReference,
            ref FontStyle                style,
            ref FontWeight               weight,
            ref FontStretch              stretch
            )
        {
            if (canonicalFamilyReference == null || object.ReferenceEquals(canonicalFamilyReference, CanonicalFontFamilyReference.Unresolved))
            {
                // no canonical name, e.g., because the friendly name was an empty string
                // or could not be canonicalized
                return null;
            }
 
            try
            {
                FamilyCollection familyCollection;
 
                if (canonicalFamilyReference.LocationUri == null && canonicalFamilyReference.EscapedFileName == null)
                {
                    // No explicit location; use the default family collection.
                    familyCollection = _defaultFamilyCollection;
                }
                else if (canonicalFamilyReference.LocationUri != null)
                {
                    // Look in the location specified by the font family reference.
                    familyCollection = FamilyCollection.FromUri(canonicalFamilyReference.LocationUri);
                }
                else // canonicalFamilyReference.EscapedFileName != null
                {
                    // Look in the specified file in the Windows Fonts folder
                    // Note: CanonicalFamilyReference.EscapedFileName is safe to combine with Util.WindowsFontsUriObject because CanonicalFamilyReference guarantees that it will be a simple filename
                    // without relative path or directory components.
                    Uri locationUri = new Uri(Util.WindowsFontsUriObject, canonicalFamilyReference.EscapedFileName);
                    familyCollection = FamilyCollection.FromWindowsFonts(locationUri);
                }
 
                IFontFamily fontFamily = familyCollection.LookupFamily(
                    canonicalFamilyReference.FamilyName,
                    ref style,
                    ref weight,
                    ref stretch
                );
                return fontFamily;
            }
            // The method returns null in case of malformed/non-existent fonts and we fall back to the next font.
            // Therefore, we can disable PreSharp warning about empty catch bodies.
#pragma warning disable 6502
            catch (FileFormatException)
            {
                // malformed font file
            }
            catch (IOException)
            {
                // canonical name points to a place that doesn't exist or can't be read for some reason
            }
            catch (UnauthorizedAccessException)
            {
                // canonical name points to a place caller doesn't have permission to access
            }
            catch (ArgumentException)
            {
                // canonical name points to a valid Uri that doesn't point to a well formed
                // OS local path
            }
            catch (NotSupportedException)
            {
                // canonical name points to a Uri that specifies an unregistered scheme
            }
            catch (UriFormatException)
            {
                // canonical name points to a malformed Uri
            }
#pragma warning restore 6502
            // we want to fall back to the default fallback font instead of crashing
            return null;
        }
 
        #endregion
    }
}