File: Core\CSharp\MS\Internal\FontCache\CanonicalFontFamilyReference.cs
Project: wpf\src\PresentationCore.csproj (PresentationCore)
//-----------------------------------------------------------------------
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  Description: The CanonicalFontFamilyReference class is the internal representation of a font
//               family reference. "Canonical" in this case means it has a normalized form and
//               the location part (if any) has been resolved to an absolute URI.
//
//               See spec at http://team/sites/Avalon/Specs/Font%20Family%20References.doc
//
//  History:
//      1-30-2006 : niklasb - Created
//
//------------------------------------------------------------------------
 
using System;
using MS.Internal;
using System.Security;
 
namespace MS.Internal.FontCache
{
    internal sealed class CanonicalFontFamilyReference
    {
        /// <summary>
        /// Create a CanonicalFontFamilyReference given a base URI and string.
        /// </summary>
        /// <param name="baseUri">Base URI used to resolve the location part, if it is relative.</param>
        /// <param name="normalizedString">Font family reference string, in the normalized form returned 
        /// by Util.GetNormalizedFontFamilyReference.</param>
        /// <returns>Returns a new CanonicalFontFamilyReference or CanonicalFontFamilyReference.Unresolved.</returns>
        /// <SecurityNote>
        /// Critical - Calls critical CanonicalFontFamilyReference ctor. 
        ///          - Makes an implicit security decision (clients may demand on a Uri based on LocationUri and EscapedFileName).
        /// Safe     - Uses critical Util.IsReferenceToWindowsFonts to skip critical ctor call for unsafe inputs
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        public static CanonicalFontFamilyReference Create(Uri baseUri, string normalizedString)
        {
            string locationString;
            string escapedFamilyName;
 
            if (SplitFontFamilyReference(normalizedString, out locationString, out escapedFamilyName))
            {
                Uri absoluteUri = null;
                string fileName = null;
                bool resolved = false;
 
                if (locationString == null || Util.IsReferenceToWindowsFonts(locationString))
                {
                    // No location (e.g., "#Arial") or file-name-only location (e.g., "arial.ttf#Arial")
                    fileName = locationString;
                    resolved = true;
                }
                else
                {
                    if (Uri.TryCreate(locationString, UriKind.Absolute, out absoluteUri))
                    {
                        // Location is an absolute URI. Make sure it's a supported scheme.
                        resolved = Util.IsSupportedSchemeForAbsoluteFontFamilyUri(absoluteUri);
                    }
                    else if (baseUri != null && Util.IsEnumerableFontUriScheme(baseUri))
                    {
                        // Location is relative to the base URI.
                        resolved = Uri.TryCreate(baseUri, locationString, out absoluteUri);
                    }
                }
 
                if (resolved)
                {
                    string unescapeFamilyName = Uri.UnescapeDataString(escapedFamilyName);
                    if (fileName != null)
                    {
                        return new CanonicalFontFamilyReference(fileName, unescapeFamilyName);
                    }
                    else
                    {
                        return new CanonicalFontFamilyReference(absoluteUri, unescapeFamilyName);
                    }
                }
            }
 
            return _unresolved;
        }
 
        /// <summary>
        /// Represents a font family reference that could not be resolved, e.g., because of an 
        /// invalid location or unsupported scheme.
        /// </summary>
        public static CanonicalFontFamilyReference Unresolved
        {
            get { return _unresolved; }
        }
 
        /// <summary>
        /// Font family name. This string is not URI encoded (escaped).
        /// </summary>
        public string FamilyName
        {
            get { return _familyName; }
        }
 
        /// <summary>
        /// If a font family reference's location part comprises a file name only (e.g., "arial.ttf#Arial")
        /// this property is the URI-encoded file name. In this case, the implied location of the file is
        /// the default Windows Fonts folder and the LocationUri property is null. In all other cases,
        /// this property is null.
        /// </summary>
        /// <SecurityNote>
        /// Critical - Sets critical member that can be used to control the path fonts are loaded from. 
        /// </SecurityNote>
        public string EscapedFileName
        {
            get;
 
            [SecurityCritical]
            private set;
        }
 
        /// <summary>
        /// Gets the font location if a specific location is given; otherwise returns null indicating
        /// the default Windows Fonts folder.
        /// </summary>
        public Uri LocationUri
        {
            get { return _absoluteLocationUri; }
        }
 
        public bool Equals(CanonicalFontFamilyReference other)
        {
            return other != null &&
                other._absoluteLocationUri == _absoluteLocationUri &&
                other.EscapedFileName == EscapedFileName &&
                other._familyName == _familyName;
        }
 
        public override bool Equals(object obj)
        {
            return Equals(obj as CanonicalFontFamilyReference);
        }
 
        public override int GetHashCode()
        {
            if (_absoluteLocationUri == null && EscapedFileName == null)
            {
                // Typical case where no location is specified
                return _familyName.GetHashCode();
            }
            else
            {
                // Either we have a URI or a file name, never both
                int hash = (_absoluteLocationUri != null) ? 
                    _absoluteLocationUri.GetHashCode() : 
                    EscapedFileName.GetHashCode();
 
                // Combine the location hash with the family name hash
                hash = HashFn.HashMultiply(hash) + _familyName.GetHashCode();
                return HashFn.HashScramble(hash);
            }
        }
 
        /// <SecurityNote>
        /// Critical - Sets critical member that can be used to control the path fonts are loaded from. 
        /// </SecurityNote>
        [SecurityCritical]
        private CanonicalFontFamilyReference(string escapedFileName, string familyName)
        {
            EscapedFileName = escapedFileName;
            _familyName = familyName;
        }
 
        private CanonicalFontFamilyReference(Uri absoluteLocationUri, string familyName)
        {
            _absoluteLocationUri = absoluteLocationUri;
            _familyName = familyName;
        }
 
        private static bool SplitFontFamilyReference(string normalizedString, out string locationString, out string escapedFamilyName)
        {
            int familyNameIndex;
 
            if (normalizedString[0] == '#')
            {
                locationString = null;
                familyNameIndex = 1;
            }
            else
            {
                int i = normalizedString.IndexOf('#');
                locationString = normalizedString.Substring(0, i);
                familyNameIndex = i + 1;
            }
 
            if (familyNameIndex < normalizedString.Length)
            {
                escapedFamilyName = normalizedString.Substring(familyNameIndex);
                return true;
            }
            else
            {
                escapedFamilyName = null;
                return false;
            }
        }
 
        private Uri     _absoluteLocationUri;
        private string  _familyName;
        private static readonly CanonicalFontFamilyReference _unresolved = new CanonicalFontFamilyReference((Uri)null, string.Empty);
    }
}