FontDifferentiator.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Core / CSharp / MS / Internal / FontFace / FontDifferentiator.cs / 1 / FontDifferentiator.cs

                            //---------------------------------------------------------------------------- 
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Description: FontDifferentiator class handles parsing font family and face names 
// and adjusting stretch, weight and style values.
// 
// History: 
//  11/10/2005 : mleonov - Started integration from a prototype application created by DBrown.
// 
//---------------------------------------------------------------------------

using System;
using System.Collections; 
using System.Collections.Generic;
using System.ComponentModel; 
using System.Diagnostics; 
using System.Globalization;
using System.IO; 
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions; 
using System.Text;
using System.Text.RegularExpressions; 
using System.Windows; 
using System.Windows.Media;
using System.Windows.Markup;    // for XmlLanguage 

using MS.Win32;
using MS.Utility;
using MS.Internal; 
using MS.Internal.FontCache;
using MS.Internal.FontFace; 
using MS.Internal.PresentationCore; 

namespace MS.Internal.FontFace 
{
    /// 
    /// FontDifferentiator class handles parsing font family and face names
    /// and adjusting stretch, weight and style values. 
    /// 
    internal static class FontDifferentiator 
    { 
        /// 
        /// We prevent this method from being inlined because FontDifferentiator brings in regular expressions, which introduce a one time working set hit. 
        /// 
        [MethodImpl(MethodImplOptions.NoInlining)]
        internal static IDictionary AppendSimulationsToFaceNames(IDictionary faceNames, StyleSimulations styleSimulations)
        { 
            Debug.Assert(styleSimulations != StyleSimulations.None);
 
            int langId; 
            string localizedName;
            FindCanonicalName(faceNames, 0, out langId, out localizedName); 

            if ((styleSimulations & StyleSimulations.BoldSimulation) != 0)
                AppendBoldSimulation(ref localizedName);
 
            if ((styleSimulations & StyleSimulations.ItalicSimulation) != 0)
                AppendObliqueSimulation(ref localizedName); 
 
            Dictionary newFaceNames = new Dictionary(1);
            newFaceNames.Add(XmlLanguage.GetLanguage(CultureInfo.GetCultureInfo(langId).IetfLanguageTag), localizedName); 

            return newFaceNames;
        }
 
        /// 
        /// We prevent this method from being inlined because FontDifferentiator brings in regular expressions, which introduce a one time working set hit. 
        ///  
        [MethodImpl(MethodImplOptions.NoInlining)]
        internal static IDictionary ConstructFaceNamesByStyleWeightStretch( 
            FontStyle style,
            FontWeight weight,
            FontStretch stretch)
        { 
            string faceName = BuildFaceName(null, style, null, weight, null, stretch, "Regular");
 
            // Default comparer calls CultureInfo.Equals, which works for our purposes. 
            Dictionary faceNames = new Dictionary(1);
            faceNames.Add(XmlLanguage.GetLanguage("en-us"), faceName); 
            return faceNames;
        }

 
        /// 
        /// We prevent this method from being inlined because FontDifferentiator brings in regular expressions, which introduce a one time working set hit. 
        ///  
        /// 
        /// Critical: accesses critical fontUri field. 
        /// TreatAsSafe: fontUri is used only for a comparison.
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        [MethodImpl(MethodImplOptions.NoInlining)] 
        internal static void ResolveFaceConflicts(List familyList, SortedList frequentStrings, FamilyCollection familyCollection)
        { 
            foreach (FamilyCollection.BaseFamily baseFamily in familyList) 
            {
                FamilyCollection.PhysicalFamily physicalFamily = baseFamily as FamilyCollection.PhysicalFamily; 
                // At this point we have no composite/physical face conflicts,
                // so we only need to be concerned about physical families potentially
                // containing duplicate faces.
                if (physicalFamily == null) 
                    continue;
 
                List physicalFaces = physicalFamily.typefaces; 

                // Sort the array of faces so that faces with the same style and stretch are adjacent and 
                // sorted by weight. We rely on this sort order in steps 1 and 2.
                physicalFaces.Sort(_styleStretchWeightComparer);

                // Step 1: Resolve stretch/weight/style conflicts. 

                // Note that physicalFaces.Count may decrease as a result of removing duplicate faces. 
                for (int i = 0; i < physicalFaces.Count; ) 
                {
                    int j = i + 1; 
                    while (j < physicalFaces.Count &&
                        _styleStretchWeightComparer.Compare(physicalFaces[i], physicalFaces[j]) == 0)
                    {
                        // Choose the best face from [i] and [j] based on, in the priority order: 
                        // a) version
                        // b) file modified date 
                        // c) file name 
                        int result = physicalFaces[i].version.CompareTo(physicalFaces[j].version);
                        if (result == 0) 
                        {
                            result = physicalFaces[i].timestamp.CompareTo(physicalFaces[j].timestamp);
                            if (result == 0)
                            { 
                                result = String.Compare(physicalFaces[i].fontUri, physicalFaces[j].fontUri, StringComparison.OrdinalIgnoreCase);
                                if (result == 0) 
                                { 
                                    result = physicalFaces[i].faceIndex - physicalFaces[j].faceIndex;
                                    if (result == 0) 
                                    {
                                        Debug.Assert(false);
                                        // I don't see how we can get here, apart from an obscure Uri escaping or case insensitivity issue.
                                        // In such cases, just deterministically pick the first face. 
                                        result = 1;
                                    } 
                                } 
                            }
                        } 

                        // if result is less than zero, j contains a better match
                        if (result < 0)
                            physicalFaces[i] = physicalFaces[j]; 

                        physicalFaces.RemoveAt(j); 
                        // Now j contains the index of the next candidate face. 
                    }
                    // At this point j is either at the end of the list, or corresponds to the first face not equal to i'th. 
                    i = j;
                }

                // Step 2: Add simulated bold faces. 
                // Note that physicalFaces.Count may increase as a result of adding simulated faces.
                for (int i = 0; i < physicalFaces.Count; ) 
                { 
                    // In a set of families that have the same stretch and style,
                    // i points to the face with minimum weight, 
                    // j - 1 points to the face with maximum weight.
                    int j = i + 1;
                    while (j < physicalFaces.Count &&
                        physicalFaces[i].style == physicalFaces[j].style && 
                        physicalFaces[i].stretch == physicalFaces[j].stretch)
                    { 
                        ++j; 
                    }
                    // Where the heaviest weight is >= 350 and <= 550, 
                    // add a bold variant at 700.
                    FamilyCollection.PhysicalFace heaviestFace = physicalFaces[j - 1];
                    if (350 <= heaviestFace.weight.ToOpenTypeWeight() &&
                        heaviestFace.weight.ToOpenTypeWeight() <= 550) 
                    {
                        // Add a simulated bold weight 
                        FamilyCollection.PhysicalFace boldFace = heaviestFace.Clone(); 

                        // At this point the face list contains only non-simulated faces. 
                        Debug.Assert(heaviestFace.styleSimulations == StyleSimulations.None);
                        boldFace.styleSimulations = StyleSimulations.BoldSimulation;
                        boldFace.weight = FontWeights.Bold;
 
                        int langId;
                        string localizedName; 
                        FindCanonicalName(heaviestFace.names, 0, out langId, out localizedName); 

                        AppendBoldSimulation(ref localizedName); 

                        // Append the word "Bold" to the face name.
                        boldFace.names = new LocalizedName[] { new LocalizedName(
                            XmlLanguage.GetLanguage(CultureInfo.GetCultureInfo(langId).IetfLanguageTag), 
                            localizedName)
                        }; 
 
                        physicalFaces.Insert(j, boldFace);
                        ++j; 
                    }
                    i = j;
                }
 
                // Step 3: Add simulated oblique faces.
                // Note that physicalFaces.Count may increase as a result of adding simulated faces. 
 
                // Sort the array of faces so that faces with the same weight and stretch are adjacent and
                // sorted by style. 
                physicalFaces.Sort(_stretchWeightStyleComparer);

                for (int i = 0; i < physicalFaces.Count; )
                { 
                    // In a set of families that have the same stretch and weight,
                    // i points to the face with minimum slant, 
                    // j - 1 points to the face with maximum slant. 
                    // We can have the following styles in the [i..j-1] range:
                    // [Normal], [Oblique], [Italic] 
                    int j = i + 1;
                    while (j < physicalFaces.Count &&
                        physicalFaces[i].weight == physicalFaces[j].weight &&
                        physicalFaces[i].stretch == physicalFaces[j].stretch) 
                    {
                        ++j; 
                    } 

                    // add the simulated oblique face based on the regular one. 
                    FamilyCollection.PhysicalFace leastSlantedFace = physicalFaces[i];

                    // If the regular face is available AND
                    // the oblique face is not present 
                    // (i.e. no more faces with the same stretch/weight OR next face is not oblique)
                    if (leastSlantedFace.style == FontStyles.Normal && 
                        (j == i + 1 || physicalFaces[i + 1].style != FontStyles.Oblique)) 
                    {
                        // Add a simulated oblique style. 
                        FamilyCollection.PhysicalFace obliqueFace = leastSlantedFace.Clone();

                        // At this point the face list contains only non-simulated faces.
                        obliqueFace.styleSimulations |= StyleSimulations.ItalicSimulation; 
                        obliqueFace.style = FontStyles.Oblique;
 
                        int langId; 
                        string localizedName;
                        FindCanonicalName(leastSlantedFace.names, 0, out langId, out localizedName); 

                        AppendObliqueSimulation(ref localizedName);
                        obliqueFace.names = new LocalizedName[] { new LocalizedName(
                            XmlLanguage.GetLanguage(CultureInfo.GetCultureInfo(langId).IetfLanguageTag), 
                            localizedName)
                        }; 
 
                        physicalFaces.Insert(i + 1, obliqueFace);
                        ++j; 
                    }
                    i = j;
                }
 
                // Step 4: Resolve face name conflicts and make name entries in Family point to the face information.
 
                physicalFamily.faceNames = new SortedDictionary(LocalizedName.NameComparer); 

                foreach (FamilyCollection.PhysicalFace face in physicalFaces) 
                {
                    foreach (LocalizedName faceName in face.names)
                    {
                        // At this point it's possible to overwrite a name entry for a previous face. 
                        // While this is not ideal, the face will still be picked up by the enumeration API and
                        // family + stretch/weight/style selection. 
                        physicalFamily.faceNames[faceName] = face; 
                        familyCollection.SaveLocalizedString(faceName, frequentStrings);
                    } 
                }
            }
        }
 
        /// 
        /// We prevent this method from being inlined because FontDifferentiator brings in regular expressions, which introduce a one time working set hit. 
        ///  
        [MethodImpl(MethodImplOptions.NoInlining)]
        internal static void AdjustFamilyAndFaceInformation( 
                ref TrueTypeFontDriver.ParsedNameTable nameTable,
                ref FontStyle fontStyle,
                ref FontWeight fontWeight,
                ref FontStretch fontStretch 
            )
        { 
            int canonicalLanguage; 
            string canonicalFamilyName;
            string canonicalSubFamilyName; 
            string canonicalLegacyFamilyName;
            string canonicalLegacySubFamilyName;

            if (!DetermineCanonicalNames( 
                ref nameTable,
                out canonicalLanguage, 
                out canonicalFamilyName, 
                out canonicalSubFamilyName,
                out canonicalLegacyFamilyName, 
                out canonicalLegacySubFamilyName))
            {
                // Couldn't determine canonical names, don't attempt to adjust family and face names.
                return; 
            }
 
            string adjustedFamilyName, parsedStyleName, parsedWeightName, parsedStretchName, regularFaceName; 
            FontStyle suggestedStyle;
            FontWeight suggestedWeight; 
            FontStretch suggestedStretch;
            ExtractStyleWeightAndStretchFromNames(
                canonicalFamilyName,
                canonicalSubFamilyName, 
                canonicalLegacyFamilyName,
                canonicalLegacySubFamilyName, 
                fontWeight, 
                out adjustedFamilyName,
                out parsedStyleName, 
                out parsedWeightName,
                out parsedStretchName,
                out regularFaceName,
                out suggestedStyle, 
                out suggestedWeight,
                out suggestedStretch 
            ); 

            // Take account of weight style and stretch information from the GlyphTypeface 

            if (parsedWeightName != null)
            {
                // We have weight both directly from the face, and from interpreting the face name 

                // Accept font os/2 value if: 
                // 1: Both font value and parsed value are on the same side of normal/medium axis, and the font value is not set to Bold. 
                // or
                // 2: Both are set to normal/medium values. 
                // or
                // 3: The difference between font and parsed values doesn't exceed 150, and the font value is not normal.
                if (fontWeight < FontWeights.Normal && suggestedWeight < FontWeights.Normal ||
                    fontWeight > FontWeights.Medium && suggestedWeight > FontWeights.Medium && fontWeight != FontWeights.Bold || 
                    (fontWeight == FontWeights.Normal || fontWeight == FontWeights.Medium) &&
                    (suggestedWeight == FontWeights.Normal || suggestedWeight == FontWeights.Medium) || 
                    fontWeight != FontWeights.Normal && fontWeight != FontWeights.Medium && fontWeight != FontWeights.Bold && 
                    fontWeight.ToOpenTypeWeight() >= suggestedWeight.ToOpenTypeWeight() - 150 &&
                    fontWeight.ToOpenTypeWeight() <= suggestedWeight.ToOpenTypeWeight() + 150) 
                {
                    // The weight name is reasonably close to the weight value. Assume the weight value
                    // is correct.
                } 
                else
                { 
                    // The face name and the weight differ significantly. Assume the font weight value is incorrect: 
                    // fonts with less common weights often use weight values of regular and bold to make many apps
                    // bold button work easily. That's fine for win32, but Avalon wants the correct weight value. 
                    // We ignore the erroneous published weight value and stick with the standard weight value
                    // for the name we recognised.
                    fontWeight = suggestedWeight;
                } 
            }
 
            if (parsedStretchName != null) 
            {
                // We have Stretch both directly from the face, and from interpreting the face name 

                // Accept font os/2 value if both font value and parsed value are on the same side of normal axis.
                if (fontStretch < FontStretches.Normal && suggestedStretch < FontStretches.Normal ||
                    fontStretch > FontStretches.Normal && suggestedStretch > FontStretches.Normal) 
                {
                    // Both stretch values are on the same side of normal, or 
                    // the Stretch name is reasonably close to the non-Normal Stretch value. Assume the Stretch value 
                    // is correct in the font.
                } 
                else
                {
                    // The face name and the Stretch differ significantly. Assume the font Stretch value is incorrect.
                    // We ignore the erroneous published Stretch value and stick with the standard Stretch value 
                    // for the name we recognised.
                    fontStretch = suggestedStretch; 
                } 
            }
 
            if (parsedStyleName != null)
            {
                // We have Style both directly from the face, and from interpreting the face name
                if (fontStyle != suggestedStyle) 
                {
                    // The face name and the Style differ significantly. Assume the font Style value is incorrect. 
                    // We ignore the erroneous published Style value and stick with the standard Style value 
                    // for the name we recognised.
                    fontStyle = suggestedStyle; 
                }
            }

            // Handle Frutiger numbering of the face name strings. 

            if (String.Compare(adjustedFamilyName, canonicalFamilyName, StringComparison.OrdinalIgnoreCase) != 0) 
            { 
                // Extract two or three digit number in the beginning of the preferred subfamily string.
                Match match = _frutigerFacePattern.Match(canonicalSubFamilyName); 
                if (match.Success)
                {
                    int faceNumber;
                    if (int.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out faceNumber)) 
                    {
                        Debug.Assert(faceNumber >= 0 && faceNumber <= 999); 
 
                        if (FrutigerMatch(faceNumber, fontStyle, fontWeight, fontStretch) ||
                            LinotypeUniversMatch(faceNumber, fontStyle, fontWeight, fontStretch)) 
                        {
                            // Remove the recognized Frutiger number from the family name, along with the preceding whitespace.
                            // Require either whitespace or end of line after the number.
                            Regex numberPattern = new Regex( 
                                "(" + RequiredWhitespace +
                                faceNumber.ToString(CultureInfo.InvariantCulture) + 
                                ")(" + RequiredWhitespace + "|$)", 
                                RegexOptions.CultureInvariant | RegexOptions.RightToLeft);
 
                            match = numberPattern.Match(adjustedFamilyName);
                            if (match.Success)
                            {
                                // Strip the number along with preceding whitespace from the family name. 
                                adjustedFamilyName = adjustedFamilyName.Remove(
                                    match.Groups[1].Index, 
                                    match.Groups[1].Length 
                                );
                            } 
                            else
                            {
                                // The Frutiger face number match must succeed because the face number gets migrated to the family name
                                // by the differentiation algorithm. Anything else would indicate a bug. 
                                Debug.Assert(false);
                            } 
                        } 
                    }
                } 
            }

            // Preferred family was not set correctly.
            if (String.Compare(canonicalFamilyName, adjustedFamilyName, StringComparison.OrdinalIgnoreCase) != 0) 
            {
                if (canonicalLegacyFamilyName != null && 
                    String.Compare(canonicalLegacyFamilyName, adjustedFamilyName, StringComparison.OrdinalIgnoreCase) == 0) 
                {
                    // Family name we decided to use has a corresponding entry in the legacy name table. In that case, 
                    // we can safely use legacy family name localizations from the font.
                    nameTable.familyNames = nameTable.win32FamilyNames;
                    nameTable.faceNames = nameTable.win32faceNames;
                } 
                else
                { 
                    // Family name we decided to use doesn't have a corresponding name table entry. 
                    // In this case, we need to discard localized versions.
 
                    XmlLanguage canonicalXmlLanguage;

                    try
                    { 
                        canonicalXmlLanguage = XmlLanguage.GetLanguage(CultureInfo.GetCultureInfo(canonicalLanguage).IetfLanguageTag);
                    } 
                    catch (ArgumentException) 
                    {
                        // Font has invalid language tag, don't attempt to adjust name information. 
                        return;
                    }

                    nameTable.familyNames = new LocalizedName[1] { new LocalizedName(canonicalXmlLanguage, adjustedFamilyName) }; 

                    string adjustedFaceName = BuildFaceName( 
                        parsedStyleName, 
                        fontStyle,
                        parsedWeightName, 
                        fontWeight,
                        parsedStretchName,
                        fontStretch,
                        regularFaceName 
                    );
 
                    nameTable.faceNames = new LocalizedName[1] { new LocalizedName(canonicalXmlLanguage, adjustedFaceName) }; 
                }
            } 
        }

        private static string BuildFaceName(
            string parsedStyleName, 
            FontStyle fontStyle,
            string parsedWeightName, 
            FontWeight fontWeight, 
            string parsedStretchName,
            FontStretch fontStretch, 
            string regularFaceName
            )
        {
            if (parsedWeightName == null && fontWeight != FontWeights.Normal) 
                parsedWeightName = ((IFormattable)fontWeight).ToString(null, CultureInfo.InvariantCulture);
 
            if (parsedStretchName == null && fontStretch != FontStretches.Normal) 
                parsedStretchName = ((IFormattable)fontStretch).ToString(null, CultureInfo.InvariantCulture);
 
            if (parsedStyleName == null && fontStyle != FontStyles.Normal)
                parsedStyleName = ((IFormattable)fontStyle).ToString(null, CultureInfo.InvariantCulture);

            // Build correct face string. 
            // Set the initial capacity to be able to hold the word "Regular".
            StringBuilder faceNameBuilder = new StringBuilder(7); 
 
            if (parsedStretchName != null)
            { 
                faceNameBuilder.Append(parsedStretchName);
            }

            if (parsedWeightName != null) 
            {
                if (faceNameBuilder.Length > 0) 
                { 
                    faceNameBuilder.Append(" ");
                } 
                faceNameBuilder.Append(parsedWeightName);
            }

            if (parsedStyleName != null) 
            {
                if (faceNameBuilder.Length > 0) 
                { 
                    faceNameBuilder.Append(" ");
                } 
                faceNameBuilder.Append(parsedStyleName);
            }

            if (faceNameBuilder.Length == 0) 
            {
                faceNameBuilder.Append(regularFaceName); 
            } 

            return faceNameBuilder.ToString(); 
        }

        private static void AppendBoldSimulation(ref string faceName)
        { 
            // Extract "Regular" in any case.
            Match regularMatch = _regularPattern.Match(faceName); 
            if (regularMatch.Success) 
            {
                // Save the original face name length for a subsequent comparison. 
                int originalFaceNameLength = faceName.Length;

                // Remove the name and spacing.
                faceName = faceName.Remove( 
                    regularMatch.Index,
                    regularMatch.Length 
                ); 

                // If the match was in the middle of a string, add a single space. 
                if (regularMatch.Index > 0 && regularMatch.Index + regularMatch.Length < originalFaceNameLength)
                    faceName = faceName.Insert(regularMatch.Index, " ");
            }
 
            // Extract weight patterns.
            // Note that we use a slightly different set of patterns than for family+face analysis: 
            // 1: The weight substring can now be at the start of the string, because we are dealing with face name only. 
            // 2: We don't look for "Ultra" weight string, because Ultra can be used in the stretch string too.
            // In general case we address this by searching for weight patterns as the last step. 
            // In the simulation case it's OK because emboldening cannot happen for FontWeights.UltraBold
            // 3: As an optimization, we don't look at Black, ExtraBold and ExtraBlack patterns either,
            // as our code doesn't embolden them (please refer to ResolveFaceConflicts, the weight will be between 350 and 550).
            string parsedWeightName; 
            FontWeight suggestedWeight;
            ExtractFaceAspect(_weightSimulationPatterns, ref faceName, out parsedWeightName, out suggestedWeight); 
 
            // Append the word "Bold" to the face name.
            faceName = faceName.Trim(); 

            if (!String.IsNullOrEmpty(faceName))
                faceName = faceName + ' ';
 
            faceName = faceName + "Bold";
        } 
 
        private static void AppendObliqueSimulation(ref string faceName)
        { 
            // Extract "Regular" in any case.
            Match regularMatch = _regularPattern.Match(faceName);
            if (regularMatch.Success)
            { 
                // Save the original face name length for a subsequent comparison.
                int originalFaceNameLength = faceName.Length; 
 
                // Remove the name and spacing.
                faceName = faceName.Remove( 
                    regularMatch.Index,
                    regularMatch.Length
                );
 
                // If the match was in the middle of a string, add a single space.
                if (regularMatch.Index > 0 && regularMatch.Index + regularMatch.Length < originalFaceNameLength) 
                    faceName = faceName.Insert(regularMatch.Index, " "); 
            }
 
            // Note that, unlike in bold simulation case, we don't have to search for other style patterns,
            // because we don't apply oblique simulation to Italic and Oblique faces.

            // Append the word "Oblique" to the face name. 
            faceName = faceName.Trim();
 
            if (!String.IsNullOrEmpty(faceName)) 
                faceName = faceName + ' ';
            faceName = faceName + "Oblique"; 
        }

        private static bool DetermineCanonicalNames(
            ref TrueTypeFontDriver.ParsedNameTable face,    // Input 
            out int canonicalLanguage,                      // Output
            out string canonicalFamilyName,                 // Output 
            out string canonicalSubFamilyName,              // Output 
            out string canonicalLegacyFamilyName,           // Output
            out string canonicalLegacySubFamilyName         // Output 
        )
        {
            canonicalFamilyName = null;
            canonicalSubFamilyName = null; 
            canonicalLegacyFamilyName = null;
            canonicalLegacySubFamilyName = null; 
 
            // Determine canonical naming language and canonical legacy family name for this face
 
            FindCanonicalName(face.win32FamilyNames, 0, out canonicalLanguage, out canonicalLegacyFamilyName);
            if (canonicalLegacyFamilyName == null)
            {
                // reject fonts with no Win32 family names 
                return false;
            } 
 
            if ((canonicalLanguage & 0x3ff) != 0x09)
            { 
                // If the primary lang id is not LANG_ENGLISH, font differentiator cannot use English name parsing
                // to determine style names. In such cases we don't adjust name and style information.
                // Please see PRIMARYLANGID macro in WinNls.h for the method to extract primary lang id.
                return false; 
            }
 
            // Determine legacy face name 

            int dummyLanguage; 
            FindCanonicalName(face.win32faceNames, canonicalLanguage, out dummyLanguage, out canonicalLegacySubFamilyName);
            if (canonicalLegacySubFamilyName == null)
            {
                // reject fonts with no Win32 face name for the canonical language 
                return false;
            } 
 
            // Determine preferred family and subfamily names
 
            int foundLanguage = 0;

            FindCanonicalName(face.familyNames, canonicalLanguage, out foundLanguage, out canonicalFamilyName);
            if (foundLanguage != canonicalLanguage) 
            {
                canonicalFamilyName = canonicalLegacyFamilyName; 
                canonicalLegacyFamilyName = null; 
            }
 
            FindCanonicalName(face.faceNames, canonicalLanguage, out foundLanguage, out canonicalSubFamilyName);
            if (foundLanguage != canonicalLanguage)
            {
                canonicalSubFamilyName = canonicalLegacySubFamilyName; 
                canonicalLegacySubFamilyName = null;
            } 
            return true; 
        }
 
        /// 
        /// FindCanonicalName - Choose name by language, in order of:
        /// 1) Requested language (optional)
        /// 2) US English 
        /// 3) name with lowest LangId.
        /// Returns both name and language. 
        ///  
        private static void FindCanonicalName(
            LocalizedName[] names,          // Input:  array of name/culture pairs 
            int targetLanguage, // Input:  Language to use if available (optional, use 0 if none)
            out int langId,         // Output: chosen language
            out string localizedName   // Output: name associated with chosen language
        ) 
        {
            int bestLanguage = int.MaxValue; 
            localizedName = null; 

            if (names != null) 
            {
                foreach (LocalizedName name in names)
                {
                    int language = name.Language.GetEquivalentCulture().LCID; 

                    if (language == targetLanguage) 
                    { 
                        language = -2;  // Ensure target language gets chosen first
                    } 
                    else if (language == 0x0409)
                    {
                        language = -1;  // Ensure English chosen before other language but after target language
                    } 

                    if (language < bestLanguage) 
                    { 
                        bestLanguage = language;
                        localizedName = name.Name; 
                    }
                }
            }
 
            // Return chosen language
 
            if (bestLanguage == int.MaxValue) 
            {
                langId = 0; 
            }
            else if (bestLanguage == -2)
            {
                langId = targetLanguage; 
            }
            else if (bestLanguage == -1) 
            { 
                langId = 0x0409;
            } 
            else
            {
                langId = bestLanguage;
            } 
        }
 
        ///  
        /// FindCanonicalName - Choose name by language, in order of:
        /// 1) Requested langauge (optional) 
        /// 2) US English
        /// 3) name with lowest LangId.
        /// Returns both name and language.
        ///  
        private static void FindCanonicalName(
            IDictionary names,          // Input:  array of name/language pairs 
            int targetLanguage, // Input:  Language to use if available (optional, use 0 if none) 
            out int langId,         // Output: chosen language
            out string localizedName   // Output: name associated with chosen language 
        )
        {
            int bestLanguage = int.MaxValue;
            localizedName = null; 

            if (names != null) 
            { 
                foreach (KeyValuePair name in names)
                { 
                    int language = name.Key.GetEquivalentCulture().LCID;

                    if (language == targetLanguage)
                    { 
                        language = -2;  // Ensure target language gets chosen first
                    } 
                    else if (language == 0x0409) 
                    {
                        language = -1;  // Ensure English chosen before other language but after target language 
                    }

                    if (language < bestLanguage)
                    { 
                        bestLanguage = language;
                        localizedName = name.Value; 
                    } 
                }
            } 

            // Return chosen language

            if (bestLanguage == int.MaxValue) 
            {
                langId = 0; 
            } 
            else if (bestLanguage == -2)
            { 
                langId = targetLanguage;
            }
            else if (bestLanguage == -1)
            { 
                langId = 0x0409;
            } 
            else 
            {
                langId = bestLanguage; 
            }
        }

        private static bool FrutigerMatch(int faceNumber, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch) 
        {
            Debug.Assert(faceNumber >= 0 && faceNumber <= 999); 
 
            // Frutiger weight numbering scheme for the first digit in the face number.
            // 0 - never used 
            // 1 - never used
            // 2 - Linotype fonts interpret this one as UltraLight
            // 3 - Linotype fonts interpret this one as Thin, even though Thin < UltraLight per the OpenType spec
            // 4 - Light for most fonts, Book for Avenir (weight = 350) 
            // 5 - Normal or Roman for most fonts, Medium for Fairfield
            // 6 - Medium for most fonts, sometimes SemiBold or Bold 
            // 7 - Bold, sometimes ExtraBold, Black or 850 
            // 8 - Heavy, Black, ExtraBlack
            // 9 - Black, UltraBlack, ExtraBlack 
            // 10 - "Helvetica Neue" has this face, it has ExtraBlack weight.

            // Given the variety of interpretations of this number, we only enforce the relationship
            // of the weight number to Normal. 

            int impliedWeight = faceNumber / 10; 
            if (impliedWeight < 2 || impliedWeight > 10) 
                return false;
 
            if (impliedWeight < 5 && fontWeight >= FontWeights.Normal)
                return false;

            if (impliedWeight > 5 && fontWeight <= FontWeights.Normal) 
                return false;
 
            if (impliedWeight == 5 && (fontWeight < FontWeights.Normal || fontWeight > FontWeights.Medium)) 
                return false;
 
            // Linotype fonts use the Frutiger numbering scheme for the second digit:
            // Note that ISBN: 0672485435 has a Univers fonts excample that
            // interprets the second digit differently,
            // The latter interpretation is included in square brackets. 
            // 0 is unused
            // 1 is unused 
            // 2 is unused 
            // 3 is Extended (Oblique) [Extended]
            // 4 is Extended Oblique only for Helvetica Neue [Extended Oblique] 
            // 5 is Regular (Oblique) [Roman/Upright]
            // 6 is Italic [Oblique]
            // 7 is Condensed (Oblique) [Condensed]
            // 8 is unused [Condensed Oblique] 
            // 9 is unused [Extra Condensed]
 
            switch (faceNumber % 10) 
            {
                case 3: 
                    return fontStretch > FontStretches.Normal;
                case 4:
                    return fontStretch > FontStretches.Normal && fontStyle != FontStyles.Normal;
                case 5: 
                    return fontStretch == FontStretches.Normal;
                case 6: 
                    return fontStretch == FontStretches.Normal && fontStyle != FontStyles.Normal; 
                case 7:
                    return fontStretch < FontStretches.Normal; 
                case 8:
                    return fontStretch < FontStretches.Normal && fontStyle != FontStyles.Normal;
                case 9:
                    return fontStretch < FontStretches.Condensed; 
                default:
                    return false; 
            } 
        }
 
        private static bool LinotypeUniversMatch(int faceNumber, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch)
        {
            Debug.Assert(faceNumber >= 0 && faceNumber <= 999);
 
            // Linotype Univers three digit numbering scheme is described at:
            // http://www.linotype.com/6-1805-6-15548/numeration.html 
 
            // The first digit is weight, roughly corresponding to os/2 weight divided by 100:
            // 1 - Ultra Light 
            // 2 - Thin
            // 3 - Light
            // 4 - Regular
            // 5 - Medium 
            // 6 - Bold
            // 7 - Heavy 
            // 8 - Black 
            // 9 - Extra Black
 
            int impliedWeight = faceNumber / 100;

            switch (impliedWeight)
            { 
                case 1:
                case 2: 
                case 3: 
                    if (fontWeight >= FontWeights.Normal)
                        return false; 
                    break;

                case 4:
                case 5: 
                    if (fontWeight < FontWeights.Normal || fontWeight > FontWeights.Medium)
                        return false; 
                    break; 

                case 6: 
                case 7:
                case 8:
                case 9:
                    if (fontWeight <= FontWeights.Normal) 
                        return false;
                    break; 
 
                default:
                    return false; 
            }

            // The second digit is stretch:
            // 1 - Compressed 
            // 2 - Condensed
            // 3 - Normal 
            // 4 - Extended 

            int impliedStretch = (faceNumber % 100) / 10; 
            if (impliedStretch < 1 || impliedStretch > 4)
                return false;
            if (impliedStretch < 3 && fontStretch >= FontStretches.Normal)
                return false; 
            if (impliedStretch == 3 && fontStretch != FontStretches.Normal)
                return false; 
            if (impliedStretch > 3 && fontStretch <= FontStretches.Normal) 
                return false;
 
            // The third digit is:
            // 0 - Upright roman
            // 1 - Italic
            int impliedStyle = faceNumber % 10; 
            if (impliedStyle < 0 || impliedStyle > 1)
                return false; 
            if (impliedStyle == 0 && fontStyle != FontStyles.Normal) 
                return false;
            if (impliedStyle == 1 && fontStyle == FontStyles.Normal) 
                return false;

            return true;
        } 

        private static void ExtractStyleWeightAndStretchFromNames( 
            string canonicalPreferredFamilyName, 
            string canonicalPreferredSubFamilyName,
            string canonicalLegacyFamilyName, 
            string canonicalLegacySubFamilyName,
            FontWeight fontWeight,
            out string adjustedFamilyName,
            out string parsedStyleName, 
            out string parsedWeightName,
            out string parsedStretchName, 
            out string regularFaceName,             // Only used when normal weight, style and stretch 
            out FontStyle suggestedStyle,
            out FontWeight suggestedWeight, 
            out FontStretch suggestedStretch
        )
        {
            // Remove any term meaning regular from the preferred subfamily 

            Match regularMatch = _regularPattern.Match(canonicalPreferredSubFamilyName); 
            if (regularMatch.Success) 
            {
                // Extract the name without spacing. 
                regularFaceName = canonicalPreferredSubFamilyName.Substring(
                    regularMatch.Groups[2].Index,
                    regularMatch.Groups[2].Length
                ); 

                // Save the subfamily length for the subsequent comparison. 
                int originalSubfamilyLength = canonicalPreferredSubFamilyName.Length; 

                // Remove the name and spacing. 
                canonicalPreferredSubFamilyName = canonicalPreferredSubFamilyName.Remove(
                    regularMatch.Index,
                    regularMatch.Length
                ); 

                // If the match was in the middle of a string, add a single space. 
                if (regularMatch.Index > 0 && regularMatch.Index + regularMatch.Length < originalSubfamilyLength) 
                    canonicalPreferredSubFamilyName = canonicalPreferredSubFamilyName.Insert(regularMatch.Index, " ");
            } 
            else
            {
                regularFaceName = "Regular";   // Needs to be culture aware
            } 

 
            // Start with space-trimmed strings 

            canonicalPreferredFamilyName = canonicalPreferredFamilyName.Trim(); 
            canonicalPreferredSubFamilyName = canonicalPreferredSubFamilyName.Trim();

            // Create one string that combines all family and subfamily names
 
            adjustedFamilyName = canonicalPreferredFamilyName;
 
            // Only append sub family name if it is not already present as a substring separated by whitespace. 

            Regex subfamilyNamePattern = new Regex( 
                    "(" + RequiredWhitespace + "|^)" +
                    "(" + Regex.Escape(canonicalPreferredSubFamilyName) + ")" +
                    "(" + RequiredWhitespace + "|$)",
                    RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant 
                );
 
            if (!subfamilyNamePattern.Match(adjustedFamilyName).Success) 
            {
                // Only append sub family name if it is not already present. 
                adjustedFamilyName += " " + canonicalPreferredSubFamilyName;
            }

            // Extract style, weight and stretch 

            ExtractFaceAspect(_stylePatterns, ref adjustedFamilyName, out parsedStyleName, out suggestedStyle); 
 
            ExtractFaceAspect(_stretchPatterns, ref adjustedFamilyName, out parsedStretchName, out suggestedStretch);
 
            // It's important that weight patterns go last, because they check for words like "Ultra", which appear in above patterns as well.
            ExtractFaceAspect(_weightPatterns, ref adjustedFamilyName, out parsedWeightName, out suggestedWeight);

            adjustedFamilyName = adjustedFamilyName.Trim(); 

            // Popular fonts such as Kozuka Gothic Std use abbreviated suffixes to denote font weight characteristics. 
            // Since matching short words and numbers like "M", "H" and "W3" can yield many false positives, 
            // we restrict this matching to the following cases:
            // 1. Suffixes must match the subfamily name string. 
            // 2. Font OS/2 value must closely match the suffix meaning.
            // 3. Font must have both legacy and preferred names, and adjustedFamilyName should be the same as legacy name.
            // 4. No other weight matches have been detected.
 
            if (parsedWeightName == null &&
                canonicalLegacyFamilyName != null && 
                String.Compare(canonicalPreferredFamilyName, adjustedFamilyName, StringComparison.OrdinalIgnoreCase) != 0 && 
                String.Compare(canonicalLegacyFamilyName, adjustedFamilyName, StringComparison.OrdinalIgnoreCase) == 0)
            { 
                // Match additional abbreviated weight patterns.

                foreach (SubFamilyPattern pattern in _abbreviatedWeightPatterns)
                { 
                    Match match = pattern.Expression.Match(canonicalPreferredSubFamilyName);
 
                    if (!match.Success) 
                        continue;
 
                    // A hit. Make sure the entire subfamily string was hit.
                    if (match.Index != 0 || match.Length != canonicalPreferredSubFamilyName.Length)
                        continue;
 
                    // Now, match the same name in the combined subfamily string.
                    match = pattern.Expression.Match(adjustedFamilyName); 
                    if (!match.Success) 
                        continue;
 
                    // Make sure os/2 value matches the weight value.
                    if ((pattern.PatternProperties & PatternProperties.ContainsNumber) != 0)
                    {
                        // Extract the weight number from W([0-9]+) style strings. 
                        int parsedWeightValue;
                        if (!int.TryParse(match.Groups[4].Value, NumberStyles.None, CultureInfo.InvariantCulture, out parsedWeightValue)) 
                            continue; 

                        if (parsedWeightValue < 1 || parsedWeightValue > 999) 
                            continue;

                        // Fonts sometimes use values between 1 and 9 to denote weights from 100 to 900.
                        if (parsedWeightValue < 10) 
                            parsedWeightValue *= 100;
 
                        suggestedWeight = FontWeight.FromOpenTypeWeight(parsedWeightValue); 
                    }
                    else 
                    {
                        suggestedWeight = pattern.FaceAspect;
                    }
 
                    if (Math.Abs(suggestedWeight.ToOpenTypeWeight() - fontWeight.ToOpenTypeWeight()) > 100)
                        continue; 
 
                    // We have a weight match.
                    // Extract the weight string from the original string. 

                    adjustedFamilyName = adjustedFamilyName.Remove(match.Index, match.Length);

                    // Removing the pattern should keep the string trimmed. 
                    Debug.Assert(adjustedFamilyName == adjustedFamilyName.Trim());
 
                    parsedWeightName = match.Groups[2].Value; 
                    suggestedWeight = fontWeight;
                    break; 
                }
            }
        }
 
        private static bool ExtractFaceAspect(  // Extract style, weight or stretch
            SubFamilyPattern[] patterns,   // Input 
            ref string name,       // InOut (extracted name is removed from string) 
            out string aspectName, // Output
            out Aspect aspect      // Output 
        )
        {
            int nameLength = name.Length;
 
            foreach (SubFamilyPattern pattern in patterns)
            { 
                Match match = pattern.Expression.Match(name); 

                if (match.Success) 
                {
                    // A hit. Remove the matched text from the string, extract
                    // the name used for this weight, and record the weight value.
                    name = name.Remove(match.Index, match.Length); 

                    // Make sure there is a space in place of the removed string to avoid substring gluing. 
                    name = name.Insert(match.Index, " "); 
                    aspectName = match.Groups[2].Value;
                    aspect = pattern.FaceAspect; 
                    return true;
                }
            }
            aspect = default(Aspect); 
            aspectName = null;
            return false; 
        } 

        [Flags] 
        private enum PatternProperties
        {
            None = 0,
            AllowAtTheStart = 1, 
            ContainsNumber = 2
        } 
 
        /// 
        /// SubFamilyPattern includes a regular expression matching a style, weight, or stretch 
        /// and identifies the value that the pattern corresponds to.
        /// 
        private class SubFamilyPattern
        { 
            private Regex _expression;
            private Aspect _faceAspect;   // FontStyle, FontWeight or FontStretch 
            private PatternProperties _patternProperties; 

            public SubFamilyPattern(string pattern, Aspect faceAspect) 
                : this(pattern, faceAspect, PatternProperties.None)
            {}

            public SubFamilyPattern(string pattern, Aspect faceAspect, PatternProperties patternProperties) 
            {
                string beforePattern = RequiredWhitespace; 
                if ((patternProperties & PatternProperties.AllowAtTheStart) != 0) 
                    beforePattern = beforePattern + "|^";
 
                _expression = new Regex(
                    // face aspect name may optionally be suffixed with "face", e.g. "Bold face"
                    "(" + beforePattern + ")((" + pattern + ")(" + OptionalWhitespace + "Face)?)(" + RequiredWhitespace + "|$)",
                    RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.RightToLeft 
                );
                _faceAspect = faceAspect; 
                _patternProperties = patternProperties; 
            }
 
            public Regex Expression { get { return _expression; } }
            public Aspect FaceAspect { get { return _faceAspect; } }
            public PatternProperties PatternProperties { get { return _patternProperties; } }
        } 

        private const string WhitespaceClass = @"[-\.\s_]"; 
        private const string OptionalWhitespace = WhitespaceClass + "*"; 
        private const string RequiredWhitespace = WhitespaceClass + "+";
        private const string ExtraPrefix = @"(Extra|Ext)" + OptionalWhitespace; 
        private const string UltraPrefix = @"(Ultra)" + OptionalWhitespace;
        private const string ExtraOrUltraPrefix = @"(Extra|Ext|Ultra)" + OptionalWhitespace;
        private const string Condensed = @"(Cond|Condensed)";
        private const string Expanded = @"(Expanded|Extended)"; 
        private const string Compressed = @"(Compressed)";
        private const string SemiOrDemi = @"(Semi|Demi)" + OptionalWhitespace; 
        private const string Semi = @"(Semi)" + OptionalWhitespace; 
        private const string Demi = @"(Demi)" + OptionalWhitespace;
 
        // Pattern order is important. Entries with prefixes should go first, so that
        // "Semi Cond" is extracted as a whole string instead of "Cond".
        private static SubFamilyPattern[] _stretchPatterns =
        { 
            new SubFamilyPattern(
                "(" + ExtraOrUltraPrefix + Compressed + ")|" + 
                "(" + UltraPrefix + Condensed + ")", 
                FontStretches.UltraCondensed),
 
            new SubFamilyPattern(
                Compressed + "|" +
                "(" + ExtraPrefix + Condensed + ")",
                FontStretches.ExtraCondensed), 

            new SubFamilyPattern( 
                "(Narrow)|(Compact)|" + 
                "(" + Semi + Condensed + ")",
                FontStretches.SemiCondensed), 

            new SubFamilyPattern(
                "(Wide)|" +
                "(" + Semi + Expanded + ")", 
                FontStretches.SemiExpanded),
 
            new SubFamilyPattern( 
                ExtraPrefix + Expanded,
                FontStretches.ExtraExpanded), 

            new SubFamilyPattern(
                UltraPrefix + Expanded,
                FontStretches.UltraExpanded), 

            new SubFamilyPattern( 
                Condensed, 
                FontStretches.Condensed),
 
            new SubFamilyPattern(
                Expanded,
                FontStretches.Expanded)
        }; 

        private static SubFamilyPattern[] _weightPatterns = 
        { 
            new SubFamilyPattern(ExtraOrUltraPrefix + "Thin",  FontWeights.Thin),
            new SubFamilyPattern(ExtraOrUltraPrefix + "Light",  FontWeights.ExtraLight), 
            new SubFamilyPattern(SemiOrDemi + "Bold",     FontWeights.DemiBold),
            new SubFamilyPattern(ExtraOrUltraPrefix + "Bold",   FontWeights.ExtraBold),
            new SubFamilyPattern(ExtraOrUltraPrefix + "Black",   FontWeights.ExtraBlack),
 
            new SubFamilyPattern("Bold",                     FontWeights.Bold),
            new SubFamilyPattern("Thin",   FontWeights.Thin), 
            new SubFamilyPattern("Light",  FontWeights.Light), 
            new SubFamilyPattern("Medium", FontWeights.Medium),
 
            new SubFamilyPattern("Black|Heavy|Nord", FontWeights.Black),

            // Fonts such as "Franklin Gothic" use a single "Demi" string to denote Demi Bold weight.
            new SubFamilyPattern(Demi,     FontWeights.DemiBold), 

            // Fonts such as "Briem Script Std" use a single "Ultra" string to denote Ultra Bold weight. 
            new SubFamilyPattern(UltraPrefix, FontWeights.ExtraBold) 
        };
 
        // Please refer to AppendBoldSimulation() comment explaining the difference from _weightPatterns.
        private static SubFamilyPattern[] _weightSimulationPatterns =
        {
            new SubFamilyPattern(ExtraOrUltraPrefix + "Light",  FontWeights.ExtraLight, PatternProperties.AllowAtTheStart), 
            new SubFamilyPattern(SemiOrDemi + "Bold",     FontWeights.DemiBold, PatternProperties.AllowAtTheStart),
 
            new SubFamilyPattern("Bold",   FontWeights.Bold, PatternProperties.AllowAtTheStart), 
            new SubFamilyPattern("Thin",   FontWeights.Thin, PatternProperties.AllowAtTheStart),
            new SubFamilyPattern("Light",  FontWeights.Light, PatternProperties.AllowAtTheStart), 
            new SubFamilyPattern("Medium", FontWeights.Medium, PatternProperties.AllowAtTheStart),

            // Fonts such as "Franklin Gothic" use a single "Demi" string to denote Demi Bold weight.
            new SubFamilyPattern(Demi, FontWeights.DemiBold, PatternProperties.AllowAtTheStart), 
        };
 
        // Please refer to ExtractStyleWeightAndStretchFromNames() for explanation of these values. 
        private static SubFamilyPattern[] _abbreviatedWeightPatterns =
        { 
            new SubFamilyPattern("EL", FontWeights.ExtraLight, PatternProperties.AllowAtTheStart),
            new SubFamilyPattern("EB", FontWeights.ExtraBold, PatternProperties.AllowAtTheStart),
            new SubFamilyPattern("SB", FontWeights.SemiBold, PatternProperties.AllowAtTheStart),
            new SubFamilyPattern("B", FontWeights.Bold, PatternProperties.AllowAtTheStart), 
            new SubFamilyPattern("L", FontWeights.Light, PatternProperties.AllowAtTheStart),
            new SubFamilyPattern("M", FontWeights.Medium, PatternProperties.AllowAtTheStart), 
            new SubFamilyPattern("R", FontWeights.Regular, PatternProperties.AllowAtTheStart), 
            new SubFamilyPattern("H", FontWeights.Heavy, PatternProperties.AllowAtTheStart),
            new SubFamilyPattern("UH", FontWeights.UltraBlack, PatternProperties.AllowAtTheStart), 
            new SubFamilyPattern("U", FontWeights.UltraBold, PatternProperties.AllowAtTheStart),

            // The font weight specified here (new FontWeight()) doesn't really matter,
            // as we use the parsed weight number. 
            new SubFamilyPattern("W([0-9]+)", new FontWeight(), PatternProperties.AllowAtTheStart | PatternProperties.ContainsNumber)
        }; 
 
        private static SubFamilyPattern[] _stylePatterns =
        { 
            new SubFamilyPattern("ital|Ita|Italic|Kursiv|Cursive", FontStyles.Italic),
            new SubFamilyPattern("Oblique|BackSlanted|BackSlant|Slanted|Inclined",  FontStyles.Oblique)
        };
 
        private static Regex _regularPattern = new Regex(
            "(" + OptionalWhitespace + ")(Regular|Normal|Roman|Book|Upright)(" + OptionalWhitespace + ")", 
            RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant 
        );
 
        private static Regex _frutigerFacePattern = new Regex(
            "^([0-9][0-9][0-9]?)(" + RequiredWhitespace + "|$)",
            RegexOptions.CultureInvariant
        ); 

        private static readonly IComparer _styleStretchWeightComparer = new StyleStretchWeightComparer(); 
        private static readonly IComparer _stretchWeightStyleComparer = new StretchWeightStyleComparer(); 

        ///  
        /// This class sorts typefaces in an order that has the following properties:
        /// 1. Faces with the same style/weight/stretch combination are adjacent.
        /// 2. Faces with the same style and stretch are grouped together and sorted by weight.
        ///  
        private class StyleStretchWeightComparer : IComparer
        { 
            public int Compare(FamilyCollection.PhysicalFace x, FamilyCollection.PhysicalFace y) 
            {
                return CalculateScore(x) - CalculateScore(y); 
            }

            private static int CalculateScore(FamilyCollection.PhysicalFace face)
            { 
                return
                    face.style.GetStyleForInternalConstruction() * 10000 + 
                    face.stretch.ToOpenTypeStretch() * 1000 + 
                    face.weight.ToOpenTypeWeight();
            } 
        }

        /// 
        /// This class sorts typefaces in an order that has the following properties: 
        /// 1. Faces with the same style/weight/stretch combination are adjacent.
        /// 2. Faces with the same weight and stretch are grouped together and sorted by style. 
        ///  
        private class StretchWeightStyleComparer : IComparer
        { 
            public int Compare(FamilyCollection.PhysicalFace x, FamilyCollection.PhysicalFace y)
            {
                return CalculateScore(x) - CalculateScore(y);
            } 

            private static int CalculateScore(FamilyCollection.PhysicalFace face) 
            { 
                return
                    face.stretch.ToOpenTypeStretch() * 10000 + 
                    face.weight.ToOpenTypeWeight() * 10 +
                    face.style.GetStyleForInternalConstruction();
            }
        } 
    }
} 
 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//---------------------------------------------------------------------------- 
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Description: FontDifferentiator class handles parsing font family and face names 
// and adjusting stretch, weight and style values.
// 
// History: 
//  11/10/2005 : mleonov - Started integration from a prototype application created by DBrown.
// 
//---------------------------------------------------------------------------

using System;
using System.Collections; 
using System.Collections.Generic;
using System.ComponentModel; 
using System.Diagnostics; 
using System.Globalization;
using System.IO; 
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions; 
using System.Text;
using System.Text.RegularExpressions; 
using System.Windows; 
using System.Windows.Media;
using System.Windows.Markup;    // for XmlLanguage 

using MS.Win32;
using MS.Utility;
using MS.Internal; 
using MS.Internal.FontCache;
using MS.Internal.FontFace; 
using MS.Internal.PresentationCore; 

namespace MS.Internal.FontFace 
{
    /// 
    /// FontDifferentiator class handles parsing font family and face names
    /// and adjusting stretch, weight and style values. 
    /// 
    internal static class FontDifferentiator 
    { 
        /// 
        /// We prevent this method from being inlined because FontDifferentiator brings in regular expressions, which introduce a one time working set hit. 
        /// 
        [MethodImpl(MethodImplOptions.NoInlining)]
        internal static IDictionary AppendSimulationsToFaceNames(IDictionary faceNames, StyleSimulations styleSimulations)
        { 
            Debug.Assert(styleSimulations != StyleSimulations.None);
 
            int langId; 
            string localizedName;
            FindCanonicalName(faceNames, 0, out langId, out localizedName); 

            if ((styleSimulations & StyleSimulations.BoldSimulation) != 0)
                AppendBoldSimulation(ref localizedName);
 
            if ((styleSimulations & StyleSimulations.ItalicSimulation) != 0)
                AppendObliqueSimulation(ref localizedName); 
 
            Dictionary newFaceNames = new Dictionary(1);
            newFaceNames.Add(XmlLanguage.GetLanguage(CultureInfo.GetCultureInfo(langId).IetfLanguageTag), localizedName); 

            return newFaceNames;
        }
 
        /// 
        /// We prevent this method from being inlined because FontDifferentiator brings in regular expressions, which introduce a one time working set hit. 
        ///  
        [MethodImpl(MethodImplOptions.NoInlining)]
        internal static IDictionary ConstructFaceNamesByStyleWeightStretch( 
            FontStyle style,
            FontWeight weight,
            FontStretch stretch)
        { 
            string faceName = BuildFaceName(null, style, null, weight, null, stretch, "Regular");
 
            // Default comparer calls CultureInfo.Equals, which works for our purposes. 
            Dictionary faceNames = new Dictionary(1);
            faceNames.Add(XmlLanguage.GetLanguage("en-us"), faceName); 
            return faceNames;
        }

 
        /// 
        /// We prevent this method from being inlined because FontDifferentiator brings in regular expressions, which introduce a one time working set hit. 
        ///  
        /// 
        /// Critical: accesses critical fontUri field. 
        /// TreatAsSafe: fontUri is used only for a comparison.
        /// 
        [SecurityCritical, SecurityTreatAsSafe]
        [MethodImpl(MethodImplOptions.NoInlining)] 
        internal static void ResolveFaceConflicts(List familyList, SortedList frequentStrings, FamilyCollection familyCollection)
        { 
            foreach (FamilyCollection.BaseFamily baseFamily in familyList) 
            {
                FamilyCollection.PhysicalFamily physicalFamily = baseFamily as FamilyCollection.PhysicalFamily; 
                // At this point we have no composite/physical face conflicts,
                // so we only need to be concerned about physical families potentially
                // containing duplicate faces.
                if (physicalFamily == null) 
                    continue;
 
                List physicalFaces = physicalFamily.typefaces; 

                // Sort the array of faces so that faces with the same style and stretch are adjacent and 
                // sorted by weight. We rely on this sort order in steps 1 and 2.
                physicalFaces.Sort(_styleStretchWeightComparer);

                // Step 1: Resolve stretch/weight/style conflicts. 

                // Note that physicalFaces.Count may decrease as a result of removing duplicate faces. 
                for (int i = 0; i < physicalFaces.Count; ) 
                {
                    int j = i + 1; 
                    while (j < physicalFaces.Count &&
                        _styleStretchWeightComparer.Compare(physicalFaces[i], physicalFaces[j]) == 0)
                    {
                        // Choose the best face from [i] and [j] based on, in the priority order: 
                        // a) version
                        // b) file modified date 
                        // c) file name 
                        int result = physicalFaces[i].version.CompareTo(physicalFaces[j].version);
                        if (result == 0) 
                        {
                            result = physicalFaces[i].timestamp.CompareTo(physicalFaces[j].timestamp);
                            if (result == 0)
                            { 
                                result = String.Compare(physicalFaces[i].fontUri, physicalFaces[j].fontUri, StringComparison.OrdinalIgnoreCase);
                                if (result == 0) 
                                { 
                                    result = physicalFaces[i].faceIndex - physicalFaces[j].faceIndex;
                                    if (result == 0) 
                                    {
                                        Debug.Assert(false);
                                        // I don't see how we can get here, apart from an obscure Uri escaping or case insensitivity issue.
                                        // In such cases, just deterministically pick the first face. 
                                        result = 1;
                                    } 
                                } 
                            }
                        } 

                        // if result is less than zero, j contains a better match
                        if (result < 0)
                            physicalFaces[i] = physicalFaces[j]; 

                        physicalFaces.RemoveAt(j); 
                        // Now j contains the index of the next candidate face. 
                    }
                    // At this point j is either at the end of the list, or corresponds to the first face not equal to i'th. 
                    i = j;
                }

                // Step 2: Add simulated bold faces. 
                // Note that physicalFaces.Count may increase as a result of adding simulated faces.
                for (int i = 0; i < physicalFaces.Count; ) 
                { 
                    // In a set of families that have the same stretch and style,
                    // i points to the face with minimum weight, 
                    // j - 1 points to the face with maximum weight.
                    int j = i + 1;
                    while (j < physicalFaces.Count &&
                        physicalFaces[i].style == physicalFaces[j].style && 
                        physicalFaces[i].stretch == physicalFaces[j].stretch)
                    { 
                        ++j; 
                    }
                    // Where the heaviest weight is >= 350 and <= 550, 
                    // add a bold variant at 700.
                    FamilyCollection.PhysicalFace heaviestFace = physicalFaces[j - 1];
                    if (350 <= heaviestFace.weight.ToOpenTypeWeight() &&
                        heaviestFace.weight.ToOpenTypeWeight() <= 550) 
                    {
                        // Add a simulated bold weight 
                        FamilyCollection.PhysicalFace boldFace = heaviestFace.Clone(); 

                        // At this point the face list contains only non-simulated faces. 
                        Debug.Assert(heaviestFace.styleSimulations == StyleSimulations.None);
                        boldFace.styleSimulations = StyleSimulations.BoldSimulation;
                        boldFace.weight = FontWeights.Bold;
 
                        int langId;
                        string localizedName; 
                        FindCanonicalName(heaviestFace.names, 0, out langId, out localizedName); 

                        AppendBoldSimulation(ref localizedName); 

                        // Append the word "Bold" to the face name.
                        boldFace.names = new LocalizedName[] { new LocalizedName(
                            XmlLanguage.GetLanguage(CultureInfo.GetCultureInfo(langId).IetfLanguageTag), 
                            localizedName)
                        }; 
 
                        physicalFaces.Insert(j, boldFace);
                        ++j; 
                    }
                    i = j;
                }
 
                // Step 3: Add simulated oblique faces.
                // Note that physicalFaces.Count may increase as a result of adding simulated faces. 
 
                // Sort the array of faces so that faces with the same weight and stretch are adjacent and
                // sorted by style. 
                physicalFaces.Sort(_stretchWeightStyleComparer);

                for (int i = 0; i < physicalFaces.Count; )
                { 
                    // In a set of families that have the same stretch and weight,
                    // i points to the face with minimum slant, 
                    // j - 1 points to the face with maximum slant. 
                    // We can have the following styles in the [i..j-1] range:
                    // [Normal], [Oblique], [Italic] 
                    int j = i + 1;
                    while (j < physicalFaces.Count &&
                        physicalFaces[i].weight == physicalFaces[j].weight &&
                        physicalFaces[i].stretch == physicalFaces[j].stretch) 
                    {
                        ++j; 
                    } 

                    // add the simulated oblique face based on the regular one. 
                    FamilyCollection.PhysicalFace leastSlantedFace = physicalFaces[i];

                    // If the regular face is available AND
                    // the oblique face is not present 
                    // (i.e. no more faces with the same stretch/weight OR next face is not oblique)
                    if (leastSlantedFace.style == FontStyles.Normal && 
                        (j == i + 1 || physicalFaces[i + 1].style != FontStyles.Oblique)) 
                    {
                        // Add a simulated oblique style. 
                        FamilyCollection.PhysicalFace obliqueFace = leastSlantedFace.Clone();

                        // At this point the face list contains only non-simulated faces.
                        obliqueFace.styleSimulations |= StyleSimulations.ItalicSimulation; 
                        obliqueFace.style = FontStyles.Oblique;
 
                        int langId; 
                        string localizedName;
                        FindCanonicalName(leastSlantedFace.names, 0, out langId, out localizedName); 

                        AppendObliqueSimulation(ref localizedName);
                        obliqueFace.names = new LocalizedName[] { new LocalizedName(
                            XmlLanguage.GetLanguage(CultureInfo.GetCultureInfo(langId).IetfLanguageTag), 
                            localizedName)
                        }; 
 
                        physicalFaces.Insert(i + 1, obliqueFace);
                        ++j; 
                    }
                    i = j;
                }
 
                // Step 4: Resolve face name conflicts and make name entries in Family point to the face information.
 
                physicalFamily.faceNames = new SortedDictionary(LocalizedName.NameComparer); 

                foreach (FamilyCollection.PhysicalFace face in physicalFaces) 
                {
                    foreach (LocalizedName faceName in face.names)
                    {
                        // At this point it's possible to overwrite a name entry for a previous face. 
                        // While this is not ideal, the face will still be picked up by the enumeration API and
                        // family + stretch/weight/style selection. 
                        physicalFamily.faceNames[faceName] = face; 
                        familyCollection.SaveLocalizedString(faceName, frequentStrings);
                    } 
                }
            }
        }
 
        /// 
        /// We prevent this method from being inlined because FontDifferentiator brings in regular expressions, which introduce a one time working set hit. 
        ///  
        [MethodImpl(MethodImplOptions.NoInlining)]
        internal static void AdjustFamilyAndFaceInformation( 
                ref TrueTypeFontDriver.ParsedNameTable nameTable,
                ref FontStyle fontStyle,
                ref FontWeight fontWeight,
                ref FontStretch fontStretch 
            )
        { 
            int canonicalLanguage; 
            string canonicalFamilyName;
            string canonicalSubFamilyName; 
            string canonicalLegacyFamilyName;
            string canonicalLegacySubFamilyName;

            if (!DetermineCanonicalNames( 
                ref nameTable,
                out canonicalLanguage, 
                out canonicalFamilyName, 
                out canonicalSubFamilyName,
                out canonicalLegacyFamilyName, 
                out canonicalLegacySubFamilyName))
            {
                // Couldn't determine canonical names, don't attempt to adjust family and face names.
                return; 
            }
 
            string adjustedFamilyName, parsedStyleName, parsedWeightName, parsedStretchName, regularFaceName; 
            FontStyle suggestedStyle;
            FontWeight suggestedWeight; 
            FontStretch suggestedStretch;
            ExtractStyleWeightAndStretchFromNames(
                canonicalFamilyName,
                canonicalSubFamilyName, 
                canonicalLegacyFamilyName,
                canonicalLegacySubFamilyName, 
                fontWeight, 
                out adjustedFamilyName,
                out parsedStyleName, 
                out parsedWeightName,
                out parsedStretchName,
                out regularFaceName,
                out suggestedStyle, 
                out suggestedWeight,
                out suggestedStretch 
            ); 

            // Take account of weight style and stretch information from the GlyphTypeface 

            if (parsedWeightName != null)
            {
                // We have weight both directly from the face, and from interpreting the face name 

                // Accept font os/2 value if: 
                // 1: Both font value and parsed value are on the same side of normal/medium axis, and the font value is not set to Bold. 
                // or
                // 2: Both are set to normal/medium values. 
                // or
                // 3: The difference between font and parsed values doesn't exceed 150, and the font value is not normal.
                if (fontWeight < FontWeights.Normal && suggestedWeight < FontWeights.Normal ||
                    fontWeight > FontWeights.Medium && suggestedWeight > FontWeights.Medium && fontWeight != FontWeights.Bold || 
                    (fontWeight == FontWeights.Normal || fontWeight == FontWeights.Medium) &&
                    (suggestedWeight == FontWeights.Normal || suggestedWeight == FontWeights.Medium) || 
                    fontWeight != FontWeights.Normal && fontWeight != FontWeights.Medium && fontWeight != FontWeights.Bold && 
                    fontWeight.ToOpenTypeWeight() >= suggestedWeight.ToOpenTypeWeight() - 150 &&
                    fontWeight.ToOpenTypeWeight() <= suggestedWeight.ToOpenTypeWeight() + 150) 
                {
                    // The weight name is reasonably close to the weight value. Assume the weight value
                    // is correct.
                } 
                else
                { 
                    // The face name and the weight differ significantly. Assume the font weight value is incorrect: 
                    // fonts with less common weights often use weight values of regular and bold to make many apps
                    // bold button work easily. That's fine for win32, but Avalon wants the correct weight value. 
                    // We ignore the erroneous published weight value and stick with the standard weight value
                    // for the name we recognised.
                    fontWeight = suggestedWeight;
                } 
            }
 
            if (parsedStretchName != null) 
            {
                // We have Stretch both directly from the face, and from interpreting the face name 

                // Accept font os/2 value if both font value and parsed value are on the same side of normal axis.
                if (fontStretch < FontStretches.Normal && suggestedStretch < FontStretches.Normal ||
                    fontStretch > FontStretches.Normal && suggestedStretch > FontStretches.Normal) 
                {
                    // Both stretch values are on the same side of normal, or 
                    // the Stretch name is reasonably close to the non-Normal Stretch value. Assume the Stretch value 
                    // is correct in the font.
                } 
                else
                {
                    // The face name and the Stretch differ significantly. Assume the font Stretch value is incorrect.
                    // We ignore the erroneous published Stretch value and stick with the standard Stretch value 
                    // for the name we recognised.
                    fontStretch = suggestedStretch; 
                } 
            }
 
            if (parsedStyleName != null)
            {
                // We have Style both directly from the face, and from interpreting the face name
                if (fontStyle != suggestedStyle) 
                {
                    // The face name and the Style differ significantly. Assume the font Style value is incorrect. 
                    // We ignore the erroneous published Style value and stick with the standard Style value 
                    // for the name we recognised.
                    fontStyle = suggestedStyle; 
                }
            }

            // Handle Frutiger numbering of the face name strings. 

            if (String.Compare(adjustedFamilyName, canonicalFamilyName, StringComparison.OrdinalIgnoreCase) != 0) 
            { 
                // Extract two or three digit number in the beginning of the preferred subfamily string.
                Match match = _frutigerFacePattern.Match(canonicalSubFamilyName); 
                if (match.Success)
                {
                    int faceNumber;
                    if (int.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out faceNumber)) 
                    {
                        Debug.Assert(faceNumber >= 0 && faceNumber <= 999); 
 
                        if (FrutigerMatch(faceNumber, fontStyle, fontWeight, fontStretch) ||
                            LinotypeUniversMatch(faceNumber, fontStyle, fontWeight, fontStretch)) 
                        {
                            // Remove the recognized Frutiger number from the family name, along with the preceding whitespace.
                            // Require either whitespace or end of line after the number.
                            Regex numberPattern = new Regex( 
                                "(" + RequiredWhitespace +
                                faceNumber.ToString(CultureInfo.InvariantCulture) + 
                                ")(" + RequiredWhitespace + "|$)", 
                                RegexOptions.CultureInvariant | RegexOptions.RightToLeft);
 
                            match = numberPattern.Match(adjustedFamilyName);
                            if (match.Success)
                            {
                                // Strip the number along with preceding whitespace from the family name. 
                                adjustedFamilyName = adjustedFamilyName.Remove(
                                    match.Groups[1].Index, 
                                    match.Groups[1].Length 
                                );
                            } 
                            else
                            {
                                // The Frutiger face number match must succeed because the face number gets migrated to the family name
                                // by the differentiation algorithm. Anything else would indicate a bug. 
                                Debug.Assert(false);
                            } 
                        } 
                    }
                } 
            }

            // Preferred family was not set correctly.
            if (String.Compare(canonicalFamilyName, adjustedFamilyName, StringComparison.OrdinalIgnoreCase) != 0) 
            {
                if (canonicalLegacyFamilyName != null && 
                    String.Compare(canonicalLegacyFamilyName, adjustedFamilyName, StringComparison.OrdinalIgnoreCase) == 0) 
                {
                    // Family name we decided to use has a corresponding entry in the legacy name table. In that case, 
                    // we can safely use legacy family name localizations from the font.
                    nameTable.familyNames = nameTable.win32FamilyNames;
                    nameTable.faceNames = nameTable.win32faceNames;
                } 
                else
                { 
                    // Family name we decided to use doesn't have a corresponding name table entry. 
                    // In this case, we need to discard localized versions.
 
                    XmlLanguage canonicalXmlLanguage;

                    try
                    { 
                        canonicalXmlLanguage = XmlLanguage.GetLanguage(CultureInfo.GetCultureInfo(canonicalLanguage).IetfLanguageTag);
                    } 
                    catch (ArgumentException) 
                    {
                        // Font has invalid language tag, don't attempt to adjust name information. 
                        return;
                    }

                    nameTable.familyNames = new LocalizedName[1] { new LocalizedName(canonicalXmlLanguage, adjustedFamilyName) }; 

                    string adjustedFaceName = BuildFaceName( 
                        parsedStyleName, 
                        fontStyle,
                        parsedWeightName, 
                        fontWeight,
                        parsedStretchName,
                        fontStretch,
                        regularFaceName 
                    );
 
                    nameTable.faceNames = new LocalizedName[1] { new LocalizedName(canonicalXmlLanguage, adjustedFaceName) }; 
                }
            } 
        }

        private static string BuildFaceName(
            string parsedStyleName, 
            FontStyle fontStyle,
            string parsedWeightName, 
            FontWeight fontWeight, 
            string parsedStretchName,
            FontStretch fontStretch, 
            string regularFaceName
            )
        {
            if (parsedWeightName == null && fontWeight != FontWeights.Normal) 
                parsedWeightName = ((IFormattable)fontWeight).ToString(null, CultureInfo.InvariantCulture);
 
            if (parsedStretchName == null && fontStretch != FontStretches.Normal) 
                parsedStretchName = ((IFormattable)fontStretch).ToString(null, CultureInfo.InvariantCulture);
 
            if (parsedStyleName == null && fontStyle != FontStyles.Normal)
                parsedStyleName = ((IFormattable)fontStyle).ToString(null, CultureInfo.InvariantCulture);

            // Build correct face string. 
            // Set the initial capacity to be able to hold the word "Regular".
            StringBuilder faceNameBuilder = new StringBuilder(7); 
 
            if (parsedStretchName != null)
            { 
                faceNameBuilder.Append(parsedStretchName);
            }

            if (parsedWeightName != null) 
            {
                if (faceNameBuilder.Length > 0) 
                { 
                    faceNameBuilder.Append(" ");
                } 
                faceNameBuilder.Append(parsedWeightName);
            }

            if (parsedStyleName != null) 
            {
                if (faceNameBuilder.Length > 0) 
                { 
                    faceNameBuilder.Append(" ");
                } 
                faceNameBuilder.Append(parsedStyleName);
            }

            if (faceNameBuilder.Length == 0) 
            {
                faceNameBuilder.Append(regularFaceName); 
            } 

            return faceNameBuilder.ToString(); 
        }

        private static void AppendBoldSimulation(ref string faceName)
        { 
            // Extract "Regular" in any case.
            Match regularMatch = _regularPattern.Match(faceName); 
            if (regularMatch.Success) 
            {
                // Save the original face name length for a subsequent comparison. 
                int originalFaceNameLength = faceName.Length;

                // Remove the name and spacing.
                faceName = faceName.Remove( 
                    regularMatch.Index,
                    regularMatch.Length 
                ); 

                // If the match was in the middle of a string, add a single space. 
                if (regularMatch.Index > 0 && regularMatch.Index + regularMatch.Length < originalFaceNameLength)
                    faceName = faceName.Insert(regularMatch.Index, " ");
            }
 
            // Extract weight patterns.
            // Note that we use a slightly different set of patterns than for family+face analysis: 
            // 1: The weight substring can now be at the start of the string, because we are dealing with face name only. 
            // 2: We don't look for "Ultra" weight string, because Ultra can be used in the stretch string too.
            // In general case we address this by searching for weight patterns as the last step. 
            // In the simulation case it's OK because emboldening cannot happen for FontWeights.UltraBold
            // 3: As an optimization, we don't look at Black, ExtraBold and ExtraBlack patterns either,
            // as our code doesn't embolden them (please refer to ResolveFaceConflicts, the weight will be between 350 and 550).
            string parsedWeightName; 
            FontWeight suggestedWeight;
            ExtractFaceAspect(_weightSimulationPatterns, ref faceName, out parsedWeightName, out suggestedWeight); 
 
            // Append the word "Bold" to the face name.
            faceName = faceName.Trim(); 

            if (!String.IsNullOrEmpty(faceName))
                faceName = faceName + ' ';
 
            faceName = faceName + "Bold";
        } 
 
        private static void AppendObliqueSimulation(ref string faceName)
        { 
            // Extract "Regular" in any case.
            Match regularMatch = _regularPattern.Match(faceName);
            if (regularMatch.Success)
            { 
                // Save the original face name length for a subsequent comparison.
                int originalFaceNameLength = faceName.Length; 
 
                // Remove the name and spacing.
                faceName = faceName.Remove( 
                    regularMatch.Index,
                    regularMatch.Length
                );
 
                // If the match was in the middle of a string, add a single space.
                if (regularMatch.Index > 0 && regularMatch.Index + regularMatch.Length < originalFaceNameLength) 
                    faceName = faceName.Insert(regularMatch.Index, " "); 
            }
 
            // Note that, unlike in bold simulation case, we don't have to search for other style patterns,
            // because we don't apply oblique simulation to Italic and Oblique faces.

            // Append the word "Oblique" to the face name. 
            faceName = faceName.Trim();
 
            if (!String.IsNullOrEmpty(faceName)) 
                faceName = faceName + ' ';
            faceName = faceName + "Oblique"; 
        }

        private static bool DetermineCanonicalNames(
            ref TrueTypeFontDriver.ParsedNameTable face,    // Input 
            out int canonicalLanguage,                      // Output
            out string canonicalFamilyName,                 // Output 
            out string canonicalSubFamilyName,              // Output 
            out string canonicalLegacyFamilyName,           // Output
            out string canonicalLegacySubFamilyName         // Output 
        )
        {
            canonicalFamilyName = null;
            canonicalSubFamilyName = null; 
            canonicalLegacyFamilyName = null;
            canonicalLegacySubFamilyName = null; 
 
            // Determine canonical naming language and canonical legacy family name for this face
 
            FindCanonicalName(face.win32FamilyNames, 0, out canonicalLanguage, out canonicalLegacyFamilyName);
            if (canonicalLegacyFamilyName == null)
            {
                // reject fonts with no Win32 family names 
                return false;
            } 
 
            if ((canonicalLanguage & 0x3ff) != 0x09)
            { 
                // If the primary lang id is not LANG_ENGLISH, font differentiator cannot use English name parsing
                // to determine style names. In such cases we don't adjust name and style information.
                // Please see PRIMARYLANGID macro in WinNls.h for the method to extract primary lang id.
                return false; 
            }
 
            // Determine legacy face name 

            int dummyLanguage; 
            FindCanonicalName(face.win32faceNames, canonicalLanguage, out dummyLanguage, out canonicalLegacySubFamilyName);
            if (canonicalLegacySubFamilyName == null)
            {
                // reject fonts with no Win32 face name for the canonical language 
                return false;
            } 
 
            // Determine preferred family and subfamily names
 
            int foundLanguage = 0;

            FindCanonicalName(face.familyNames, canonicalLanguage, out foundLanguage, out canonicalFamilyName);
            if (foundLanguage != canonicalLanguage) 
            {
                canonicalFamilyName = canonicalLegacyFamilyName; 
                canonicalLegacyFamilyName = null; 
            }
 
            FindCanonicalName(face.faceNames, canonicalLanguage, out foundLanguage, out canonicalSubFamilyName);
            if (foundLanguage != canonicalLanguage)
            {
                canonicalSubFamilyName = canonicalLegacySubFamilyName; 
                canonicalLegacySubFamilyName = null;
            } 
            return true; 
        }
 
        /// 
        /// FindCanonicalName - Choose name by language, in order of:
        /// 1) Requested language (optional)
        /// 2) US English 
        /// 3) name with lowest LangId.
        /// Returns both name and language. 
        ///  
        private static void FindCanonicalName(
            LocalizedName[] names,          // Input:  array of name/culture pairs 
            int targetLanguage, // Input:  Language to use if available (optional, use 0 if none)
            out int langId,         // Output: chosen language
            out string localizedName   // Output: name associated with chosen language
        ) 
        {
            int bestLanguage = int.MaxValue; 
            localizedName = null; 

            if (names != null) 
            {
                foreach (LocalizedName name in names)
                {
                    int language = name.Language.GetEquivalentCulture().LCID; 

                    if (language == targetLanguage) 
                    { 
                        language = -2;  // Ensure target language gets chosen first
                    } 
                    else if (language == 0x0409)
                    {
                        language = -1;  // Ensure English chosen before other language but after target language
                    } 

                    if (language < bestLanguage) 
                    { 
                        bestLanguage = language;
                        localizedName = name.Name; 
                    }
                }
            }
 
            // Return chosen language
 
            if (bestLanguage == int.MaxValue) 
            {
                langId = 0; 
            }
            else if (bestLanguage == -2)
            {
                langId = targetLanguage; 
            }
            else if (bestLanguage == -1) 
            { 
                langId = 0x0409;
            } 
            else
            {
                langId = bestLanguage;
            } 
        }
 
        ///  
        /// FindCanonicalName - Choose name by language, in order of:
        /// 1) Requested langauge (optional) 
        /// 2) US English
        /// 3) name with lowest LangId.
        /// Returns both name and language.
        ///  
        private static void FindCanonicalName(
            IDictionary names,          // Input:  array of name/language pairs 
            int targetLanguage, // Input:  Language to use if available (optional, use 0 if none) 
            out int langId,         // Output: chosen language
            out string localizedName   // Output: name associated with chosen language 
        )
        {
            int bestLanguage = int.MaxValue;
            localizedName = null; 

            if (names != null) 
            { 
                foreach (KeyValuePair name in names)
                { 
                    int language = name.Key.GetEquivalentCulture().LCID;

                    if (language == targetLanguage)
                    { 
                        language = -2;  // Ensure target language gets chosen first
                    } 
                    else if (language == 0x0409) 
                    {
                        language = -1;  // Ensure English chosen before other language but after target language 
                    }

                    if (language < bestLanguage)
                    { 
                        bestLanguage = language;
                        localizedName = name.Value; 
                    } 
                }
            } 

            // Return chosen language

            if (bestLanguage == int.MaxValue) 
            {
                langId = 0; 
            } 
            else if (bestLanguage == -2)
            { 
                langId = targetLanguage;
            }
            else if (bestLanguage == -1)
            { 
                langId = 0x0409;
            } 
            else 
            {
                langId = bestLanguage; 
            }
        }

        private static bool FrutigerMatch(int faceNumber, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch) 
        {
            Debug.Assert(faceNumber >= 0 && faceNumber <= 999); 
 
            // Frutiger weight numbering scheme for the first digit in the face number.
            // 0 - never used 
            // 1 - never used
            // 2 - Linotype fonts interpret this one as UltraLight
            // 3 - Linotype fonts interpret this one as Thin, even though Thin < UltraLight per the OpenType spec
            // 4 - Light for most fonts, Book for Avenir (weight = 350) 
            // 5 - Normal or Roman for most fonts, Medium for Fairfield
            // 6 - Medium for most fonts, sometimes SemiBold or Bold 
            // 7 - Bold, sometimes ExtraBold, Black or 850 
            // 8 - Heavy, Black, ExtraBlack
            // 9 - Black, UltraBlack, ExtraBlack 
            // 10 - "Helvetica Neue" has this face, it has ExtraBlack weight.

            // Given the variety of interpretations of this number, we only enforce the relationship
            // of the weight number to Normal. 

            int impliedWeight = faceNumber / 10; 
            if (impliedWeight < 2 || impliedWeight > 10) 
                return false;
 
            if (impliedWeight < 5 && fontWeight >= FontWeights.Normal)
                return false;

            if (impliedWeight > 5 && fontWeight <= FontWeights.Normal) 
                return false;
 
            if (impliedWeight == 5 && (fontWeight < FontWeights.Normal || fontWeight > FontWeights.Medium)) 
                return false;
 
            // Linotype fonts use the Frutiger numbering scheme for the second digit:
            // Note that ISBN: 0672485435 has a Univers fonts excample that
            // interprets the second digit differently,
            // The latter interpretation is included in square brackets. 
            // 0 is unused
            // 1 is unused 
            // 2 is unused 
            // 3 is Extended (Oblique) [Extended]
            // 4 is Extended Oblique only for Helvetica Neue [Extended Oblique] 
            // 5 is Regular (Oblique) [Roman/Upright]
            // 6 is Italic [Oblique]
            // 7 is Condensed (Oblique) [Condensed]
            // 8 is unused [Condensed Oblique] 
            // 9 is unused [Extra Condensed]
 
            switch (faceNumber % 10) 
            {
                case 3: 
                    return fontStretch > FontStretches.Normal;
                case 4:
                    return fontStretch > FontStretches.Normal && fontStyle != FontStyles.Normal;
                case 5: 
                    return fontStretch == FontStretches.Normal;
                case 6: 
                    return fontStretch == FontStretches.Normal && fontStyle != FontStyles.Normal; 
                case 7:
                    return fontStretch < FontStretches.Normal; 
                case 8:
                    return fontStretch < FontStretches.Normal && fontStyle != FontStyles.Normal;
                case 9:
                    return fontStretch < FontStretches.Condensed; 
                default:
                    return false; 
            } 
        }
 
        private static bool LinotypeUniversMatch(int faceNumber, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch)
        {
            Debug.Assert(faceNumber >= 0 && faceNumber <= 999);
 
            // Linotype Univers three digit numbering scheme is described at:
            // http://www.linotype.com/6-1805-6-15548/numeration.html 
 
            // The first digit is weight, roughly corresponding to os/2 weight divided by 100:
            // 1 - Ultra Light 
            // 2 - Thin
            // 3 - Light
            // 4 - Regular
            // 5 - Medium 
            // 6 - Bold
            // 7 - Heavy 
            // 8 - Black 
            // 9 - Extra Black
 
            int impliedWeight = faceNumber / 100;

            switch (impliedWeight)
            { 
                case 1:
                case 2: 
                case 3: 
                    if (fontWeight >= FontWeights.Normal)
                        return false; 
                    break;

                case 4:
                case 5: 
                    if (fontWeight < FontWeights.Normal || fontWeight > FontWeights.Medium)
                        return false; 
                    break; 

                case 6: 
                case 7:
                case 8:
                case 9:
                    if (fontWeight <= FontWeights.Normal) 
                        return false;
                    break; 
 
                default:
                    return false; 
            }

            // The second digit is stretch:
            // 1 - Compressed 
            // 2 - Condensed
            // 3 - Normal 
            // 4 - Extended 

            int impliedStretch = (faceNumber % 100) / 10; 
            if (impliedStretch < 1 || impliedStretch > 4)
                return false;
            if (impliedStretch < 3 && fontStretch >= FontStretches.Normal)
                return false; 
            if (impliedStretch == 3 && fontStretch != FontStretches.Normal)
                return false; 
            if (impliedStretch > 3 && fontStretch <= FontStretches.Normal) 
                return false;
 
            // The third digit is:
            // 0 - Upright roman
            // 1 - Italic
            int impliedStyle = faceNumber % 10; 
            if (impliedStyle < 0 || impliedStyle > 1)
                return false; 
            if (impliedStyle == 0 && fontStyle != FontStyles.Normal) 
                return false;
            if (impliedStyle == 1 && fontStyle == FontStyles.Normal) 
                return false;

            return true;
        } 

        private static void ExtractStyleWeightAndStretchFromNames( 
            string canonicalPreferredFamilyName, 
            string canonicalPreferredSubFamilyName,
            string canonicalLegacyFamilyName, 
            string canonicalLegacySubFamilyName,
            FontWeight fontWeight,
            out string adjustedFamilyName,
            out string parsedStyleName, 
            out string parsedWeightName,
            out string parsedStretchName, 
            out string regularFaceName,             // Only used when normal weight, style and stretch 
            out FontStyle suggestedStyle,
            out FontWeight suggestedWeight, 
            out FontStretch suggestedStretch
        )
        {
            // Remove any term meaning regular from the preferred subfamily 

            Match regularMatch = _regularPattern.Match(canonicalPreferredSubFamilyName); 
            if (regularMatch.Success) 
            {
                // Extract the name without spacing. 
                regularFaceName = canonicalPreferredSubFamilyName.Substring(
                    regularMatch.Groups[2].Index,
                    regularMatch.Groups[2].Length
                ); 

                // Save the subfamily length for the subsequent comparison. 
                int originalSubfamilyLength = canonicalPreferredSubFamilyName.Length; 

                // Remove the name and spacing. 
                canonicalPreferredSubFamilyName = canonicalPreferredSubFamilyName.Remove(
                    regularMatch.Index,
                    regularMatch.Length
                ); 

                // If the match was in the middle of a string, add a single space. 
                if (regularMatch.Index > 0 && regularMatch.Index + regularMatch.Length < originalSubfamilyLength) 
                    canonicalPreferredSubFamilyName = canonicalPreferredSubFamilyName.Insert(regularMatch.Index, " ");
            } 
            else
            {
                regularFaceName = "Regular";   // Needs to be culture aware
            } 

 
            // Start with space-trimmed strings 

            canonicalPreferredFamilyName = canonicalPreferredFamilyName.Trim(); 
            canonicalPreferredSubFamilyName = canonicalPreferredSubFamilyName.Trim();

            // Create one string that combines all family and subfamily names
 
            adjustedFamilyName = canonicalPreferredFamilyName;
 
            // Only append sub family name if it is not already present as a substring separated by whitespace. 

            Regex subfamilyNamePattern = new Regex( 
                    "(" + RequiredWhitespace + "|^)" +
                    "(" + Regex.Escape(canonicalPreferredSubFamilyName) + ")" +
                    "(" + RequiredWhitespace + "|$)",
                    RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant 
                );
 
            if (!subfamilyNamePattern.Match(adjustedFamilyName).Success) 
            {
                // Only append sub family name if it is not already present. 
                adjustedFamilyName += " " + canonicalPreferredSubFamilyName;
            }

            // Extract style, weight and stretch 

            ExtractFaceAspect(_stylePatterns, ref adjustedFamilyName, out parsedStyleName, out suggestedStyle); 
 
            ExtractFaceAspect(_stretchPatterns, ref adjustedFamilyName, out parsedStretchName, out suggestedStretch);
 
            // It's important that weight patterns go last, because they check for words like "Ultra", which appear in above patterns as well.
            ExtractFaceAspect(_weightPatterns, ref adjustedFamilyName, out parsedWeightName, out suggestedWeight);

            adjustedFamilyName = adjustedFamilyName.Trim(); 

            // Popular fonts such as Kozuka Gothic Std use abbreviated suffixes to denote font weight characteristics. 
            // Since matching short words and numbers like "M", "H" and "W3" can yield many false positives, 
            // we restrict this matching to the following cases:
            // 1. Suffixes must match the subfamily name string. 
            // 2. Font OS/2 value must closely match the suffix meaning.
            // 3. Font must have both legacy and preferred names, and adjustedFamilyName should be the same as legacy name.
            // 4. No other weight matches have been detected.
 
            if (parsedWeightName == null &&
                canonicalLegacyFamilyName != null && 
                String.Compare(canonicalPreferredFamilyName, adjustedFamilyName, StringComparison.OrdinalIgnoreCase) != 0 && 
                String.Compare(canonicalLegacyFamilyName, adjustedFamilyName, StringComparison.OrdinalIgnoreCase) == 0)
            { 
                // Match additional abbreviated weight patterns.

                foreach (SubFamilyPattern pattern in _abbreviatedWeightPatterns)
                { 
                    Match match = pattern.Expression.Match(canonicalPreferredSubFamilyName);
 
                    if (!match.Success) 
                        continue;
 
                    // A hit. Make sure the entire subfamily string was hit.
                    if (match.Index != 0 || match.Length != canonicalPreferredSubFamilyName.Length)
                        continue;
 
                    // Now, match the same name in the combined subfamily string.
                    match = pattern.Expression.Match(adjustedFamilyName); 
                    if (!match.Success) 
                        continue;
 
                    // Make sure os/2 value matches the weight value.
                    if ((pattern.PatternProperties & PatternProperties.ContainsNumber) != 0)
                    {
                        // Extract the weight number from W([0-9]+) style strings. 
                        int parsedWeightValue;
                        if (!int.TryParse(match.Groups[4].Value, NumberStyles.None, CultureInfo.InvariantCulture, out parsedWeightValue)) 
                            continue; 

                        if (parsedWeightValue < 1 || parsedWeightValue > 999) 
                            continue;

                        // Fonts sometimes use values between 1 and 9 to denote weights from 100 to 900.
                        if (parsedWeightValue < 10) 
                            parsedWeightValue *= 100;
 
                        suggestedWeight = FontWeight.FromOpenTypeWeight(parsedWeightValue); 
                    }
                    else 
                    {
                        suggestedWeight = pattern.FaceAspect;
                    }
 
                    if (Math.Abs(suggestedWeight.ToOpenTypeWeight() - fontWeight.ToOpenTypeWeight()) > 100)
                        continue; 
 
                    // We have a weight match.
                    // Extract the weight string from the original string. 

                    adjustedFamilyName = adjustedFamilyName.Remove(match.Index, match.Length);

                    // Removing the pattern should keep the string trimmed. 
                    Debug.Assert(adjustedFamilyName == adjustedFamilyName.Trim());
 
                    parsedWeightName = match.Groups[2].Value; 
                    suggestedWeight = fontWeight;
                    break; 
                }
            }
        }
 
        private static bool ExtractFaceAspect(  // Extract style, weight or stretch
            SubFamilyPattern[] patterns,   // Input 
            ref string name,       // InOut (extracted name is removed from string) 
            out string aspectName, // Output
            out Aspect aspect      // Output 
        )
        {
            int nameLength = name.Length;
 
            foreach (SubFamilyPattern pattern in patterns)
            { 
                Match match = pattern.Expression.Match(name); 

                if (match.Success) 
                {
                    // A hit. Remove the matched text from the string, extract
                    // the name used for this weight, and record the weight value.
                    name = name.Remove(match.Index, match.Length); 

                    // Make sure there is a space in place of the removed string to avoid substring gluing. 
                    name = name.Insert(match.Index, " "); 
                    aspectName = match.Groups[2].Value;
                    aspect = pattern.FaceAspect; 
                    return true;
                }
            }
            aspect = default(Aspect); 
            aspectName = null;
            return false; 
        } 

        [Flags] 
        private enum PatternProperties
        {
            None = 0,
            AllowAtTheStart = 1, 
            ContainsNumber = 2
        } 
 
        /// 
        /// SubFamilyPattern includes a regular expression matching a style, weight, or stretch 
        /// and identifies the value that the pattern corresponds to.
        /// 
        private class SubFamilyPattern
        { 
            private Regex _expression;
            private Aspect _faceAspect;   // FontStyle, FontWeight or FontStretch 
            private PatternProperties _patternProperties; 

            public SubFamilyPattern(string pattern, Aspect faceAspect) 
                : this(pattern, faceAspect, PatternProperties.None)
            {}

            public SubFamilyPattern(string pattern, Aspect faceAspect, PatternProperties patternProperties) 
            {
                string beforePattern = RequiredWhitespace; 
                if ((patternProperties & PatternProperties.AllowAtTheStart) != 0) 
                    beforePattern = beforePattern + "|^";
 
                _expression = new Regex(
                    // face aspect name may optionally be suffixed with "face", e.g. "Bold face"
                    "(" + beforePattern + ")((" + pattern + ")(" + OptionalWhitespace + "Face)?)(" + RequiredWhitespace + "|$)",
                    RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.RightToLeft 
                );
                _faceAspect = faceAspect; 
                _patternProperties = patternProperties; 
            }
 
            public Regex Expression { get { return _expression; } }
            public Aspect FaceAspect { get { return _faceAspect; } }
            public PatternProperties PatternProperties { get { return _patternProperties; } }
        } 

        private const string WhitespaceClass = @"[-\.\s_]"; 
        private const string OptionalWhitespace = WhitespaceClass + "*"; 
        private const string RequiredWhitespace = WhitespaceClass + "+";
        private const string ExtraPrefix = @"(Extra|Ext)" + OptionalWhitespace; 
        private const string UltraPrefix = @"(Ultra)" + OptionalWhitespace;
        private const string ExtraOrUltraPrefix = @"(Extra|Ext|Ultra)" + OptionalWhitespace;
        private const string Condensed = @"(Cond|Condensed)";
        private const string Expanded = @"(Expanded|Extended)"; 
        private const string Compressed = @"(Compressed)";
        private const string SemiOrDemi = @"(Semi|Demi)" + OptionalWhitespace; 
        private const string Semi = @"(Semi)" + OptionalWhitespace; 
        private const string Demi = @"(Demi)" + OptionalWhitespace;
 
        // Pattern order is important. Entries with prefixes should go first, so that
        // "Semi Cond" is extracted as a whole string instead of "Cond".
        private static SubFamilyPattern[] _stretchPatterns =
        { 
            new SubFamilyPattern(
                "(" + ExtraOrUltraPrefix + Compressed + ")|" + 
                "(" + UltraPrefix + Condensed + ")", 
                FontStretches.UltraCondensed),
 
            new SubFamilyPattern(
                Compressed + "|" +
                "(" + ExtraPrefix + Condensed + ")",
                FontStretches.ExtraCondensed), 

            new SubFamilyPattern( 
                "(Narrow)|(Compact)|" + 
                "(" + Semi + Condensed + ")",
                FontStretches.SemiCondensed), 

            new SubFamilyPattern(
                "(Wide)|" +
                "(" + Semi + Expanded + ")", 
                FontStretches.SemiExpanded),
 
            new SubFamilyPattern( 
                ExtraPrefix + Expanded,
                FontStretches.ExtraExpanded), 

            new SubFamilyPattern(
                UltraPrefix + Expanded,
                FontStretches.UltraExpanded), 

            new SubFamilyPattern( 
                Condensed, 
                FontStretches.Condensed),
 
            new SubFamilyPattern(
                Expanded,
                FontStretches.Expanded)
        }; 

        private static SubFamilyPattern[] _weightPatterns = 
        { 
            new SubFamilyPattern(ExtraOrUltraPrefix + "Thin",  FontWeights.Thin),
            new SubFamilyPattern(ExtraOrUltraPrefix + "Light",  FontWeights.ExtraLight), 
            new SubFamilyPattern(SemiOrDemi + "Bold",     FontWeights.DemiBold),
            new SubFamilyPattern(ExtraOrUltraPrefix + "Bold",   FontWeights.ExtraBold),
            new SubFamilyPattern(ExtraOrUltraPrefix + "Black",   FontWeights.ExtraBlack),
 
            new SubFamilyPattern("Bold",                     FontWeights.Bold),
            new SubFamilyPattern("Thin",   FontWeights.Thin), 
            new SubFamilyPattern("Light",  FontWeights.Light), 
            new SubFamilyPattern("Medium", FontWeights.Medium),
 
            new SubFamilyPattern("Black|Heavy|Nord", FontWeights.Black),

            // Fonts such as "Franklin Gothic" use a single "Demi" string to denote Demi Bold weight.
            new SubFamilyPattern(Demi,     FontWeights.DemiBold), 

            // Fonts such as "Briem Script Std" use a single "Ultra" string to denote Ultra Bold weight. 
            new SubFamilyPattern(UltraPrefix, FontWeights.ExtraBold) 
        };
 
        // Please refer to AppendBoldSimulation() comment explaining the difference from _weightPatterns.
        private static SubFamilyPattern[] _weightSimulationPatterns =
        {
            new SubFamilyPattern(ExtraOrUltraPrefix + "Light",  FontWeights.ExtraLight, PatternProperties.AllowAtTheStart), 
            new SubFamilyPattern(SemiOrDemi + "Bold",     FontWeights.DemiBold, PatternProperties.AllowAtTheStart),
 
            new SubFamilyPattern("Bold",   FontWeights.Bold, PatternProperties.AllowAtTheStart), 
            new SubFamilyPattern("Thin",   FontWeights.Thin, PatternProperties.AllowAtTheStart),
            new SubFamilyPattern("Light",  FontWeights.Light, PatternProperties.AllowAtTheStart), 
            new SubFamilyPattern("Medium", FontWeights.Medium, PatternProperties.AllowAtTheStart),

            // Fonts such as "Franklin Gothic" use a single "Demi" string to denote Demi Bold weight.
            new SubFamilyPattern(Demi, FontWeights.DemiBold, PatternProperties.AllowAtTheStart), 
        };
 
        // Please refer to ExtractStyleWeightAndStretchFromNames() for explanation of these values. 
        private static SubFamilyPattern[] _abbreviatedWeightPatterns =
        { 
            new SubFamilyPattern("EL", FontWeights.ExtraLight, PatternProperties.AllowAtTheStart),
            new SubFamilyPattern("EB", FontWeights.ExtraBold, PatternProperties.AllowAtTheStart),
            new SubFamilyPattern("SB", FontWeights.SemiBold, PatternProperties.AllowAtTheStart),
            new SubFamilyPattern("B", FontWeights.Bold, PatternProperties.AllowAtTheStart), 
            new SubFamilyPattern("L", FontWeights.Light, PatternProperties.AllowAtTheStart),
            new SubFamilyPattern("M", FontWeights.Medium, PatternProperties.AllowAtTheStart), 
            new SubFamilyPattern("R", FontWeights.Regular, PatternProperties.AllowAtTheStart), 
            new SubFamilyPattern("H", FontWeights.Heavy, PatternProperties.AllowAtTheStart),
            new SubFamilyPattern("UH", FontWeights.UltraBlack, PatternProperties.AllowAtTheStart), 
            new SubFamilyPattern("U", FontWeights.UltraBold, PatternProperties.AllowAtTheStart),

            // The font weight specified here (new FontWeight()) doesn't really matter,
            // as we use the parsed weight number. 
            new SubFamilyPattern("W([0-9]+)", new FontWeight(), PatternProperties.AllowAtTheStart | PatternProperties.ContainsNumber)
        }; 
 
        private static SubFamilyPattern[] _stylePatterns =
        { 
            new SubFamilyPattern("ital|Ita|Italic|Kursiv|Cursive", FontStyles.Italic),
            new SubFamilyPattern("Oblique|BackSlanted|BackSlant|Slanted|Inclined",  FontStyles.Oblique)
        };
 
        private static Regex _regularPattern = new Regex(
            "(" + OptionalWhitespace + ")(Regular|Normal|Roman|Book|Upright)(" + OptionalWhitespace + ")", 
            RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant 
        );
 
        private static Regex _frutigerFacePattern = new Regex(
            "^([0-9][0-9][0-9]?)(" + RequiredWhitespace + "|$)",
            RegexOptions.CultureInvariant
        ); 

        private static readonly IComparer _styleStretchWeightComparer = new StyleStretchWeightComparer(); 
        private static readonly IComparer _stretchWeightStyleComparer = new StretchWeightStyleComparer(); 

        ///  
        /// This class sorts typefaces in an order that has the following properties:
        /// 1. Faces with the same style/weight/stretch combination are adjacent.
        /// 2. Faces with the same style and stretch are grouped together and sorted by weight.
        ///  
        private class StyleStretchWeightComparer : IComparer
        { 
            public int Compare(FamilyCollection.PhysicalFace x, FamilyCollection.PhysicalFace y) 
            {
                return CalculateScore(x) - CalculateScore(y); 
            }

            private static int CalculateScore(FamilyCollection.PhysicalFace face)
            { 
                return
                    face.style.GetStyleForInternalConstruction() * 10000 + 
                    face.stretch.ToOpenTypeStretch() * 1000 + 
                    face.weight.ToOpenTypeWeight();
            } 
        }

        /// 
        /// This class sorts typefaces in an order that has the following properties: 
        /// 1. Faces with the same style/weight/stretch combination are adjacent.
        /// 2. Faces with the same weight and stretch are grouped together and sorted by style. 
        ///  
        private class StretchWeightStyleComparer : IComparer
        { 
            public int Compare(FamilyCollection.PhysicalFace x, FamilyCollection.PhysicalFace y)
            {
                return CalculateScore(x) - CalculateScore(y);
            } 

            private static int CalculateScore(FamilyCollection.PhysicalFace face) 
            { 
                return
                    face.stretch.ToOpenTypeStretch() * 10000 + 
                    face.weight.ToOpenTypeWeight() * 10 +
                    face.style.GetStyleForInternalConstruction();
            }
        } 
    }
} 
 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK