File: System\Windows\Media\Typeface.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//
//
//
//  Contents:  Typeface 
//
//
 
using System;
using System.Globalization;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security;
using System.Windows;
using System.Windows.Media.TextFormatting;
using System.Runtime.InteropServices;
using System.ComponentModel;
 
using MS.Internal;
using MS.Internal.FontFace;
using MS.Internal.FontCache;
using MS.Internal.Shaping;
using MS.Internal.TextFormatting;
 
 
namespace System.Windows.Media
{
    /// <summary>
    /// A Typeface is a combination of family, weight, style and stretch:
    /// </summary>
    public class Typeface
    {
        private FontFamily              _fontFamily;
 
        // these _style, _weight and _stretch are only used for storing what was passed into the constructor.
        // Since FontFamily may change these values when it includes a style name implicitly,
        private FontStyle               _style;
        private FontWeight              _weight;
        private FontStretch             _stretch;        
 
        private FontFamily              _fallbackFontFamily;
 
        // Cached canonical values of the typeface. 
        private CachedTypeface         _cachedTypeface;        
 
 
        /// <summary>
        /// Construct a typeface
        /// </summary>
        /// <param name="typefaceName">font typeface name</param>
        public Typeface(
            string typefaceName
            ) 
            // assume face name is family name until we get face name resolved properly.
            : this(
                new FontFamily(typefaceName),   
                FontStyles.Normal, 
                FontWeights.Normal, 
                FontStretches.Normal
                )
        {}
 
 
 
        /// <summary>
        /// Construct a typeface
        /// </summary>
        /// <param name="fontFamily">Font family</param>
        /// <param name="style">Font style</param>
        /// <param name="weight">Boldness of font</param>
        /// <param name="stretch">Width of characters</param>
        public Typeface(
            FontFamily      fontFamily,
            FontStyle       style,
            FontWeight      weight,
            FontStretch     stretch
            )
            : this(
                fontFamily,
                style,
                weight,
                stretch,
                FontFamily.FontFamilyGlobalUI
                )
        {}
 
 
 
        /// <summary>
        /// Construct a typeface
        /// </summary>
        /// <param name="fontFamily">Font family</param>
        /// <param name="style">Font style</param>
        /// <param name="weight">Boldness of font</param>
        /// <param name="stretch">Width of characters</param>
        /// <param name="fallbackFontFamily">fallback font family</param>
        public Typeface(
            FontFamily      fontFamily,
            FontStyle       style,
            FontWeight      weight,
            FontStretch     stretch,
            FontFamily      fallbackFontFamily
            )
        {
            ArgumentNullException.ThrowIfNull(fontFamily);
 
            _fontFamily = fontFamily;
            _style = style;
            _weight = weight;
            _stretch = stretch;
            _fallbackFontFamily = fallbackFontFamily;
        }
 
 
 
        /// <summary>
        /// Font family
        /// </summary>
        public FontFamily FontFamily
        {
            get { return _fontFamily; }
        }
 
 
        /// <summary>
        /// Font weight (light, bold, etc.)
        /// </summary>
        public FontWeight Weight
        {
            get { return _weight; }
        }
 
 
        /// <summary>
        /// Font style (italic, oblique)
        /// </summary>
        public FontStyle Style
        {
            get { return _style; }
        }
 
 
        /// <summary>
        /// Font Stretch (narrow, wide, etc.)
        /// </summary>
        public FontStretch Stretch
        {
            get { return _stretch; }
        }
 
        /// <summary>
        /// Returns true if FontStyle.Oblique is algorithmically simulated by
        /// slanting glyphs. Returns false otherwise.
        /// </summary>
        public bool IsObliqueSimulated
        {
            get
            {
                return (CachedTypeface.TypefaceMetrics.StyleSimulations & StyleSimulations.ItalicSimulation) != 0;
            }
        }
 
        /// <summary>
        /// Returns true if FontStyle.Bold is algorithmically simulated by
        /// thickening glyphs. Returns false otherwise.
        /// </summary>
        public bool IsBoldSimulated
        {
            get
            {
                return (CachedTypeface.TypefaceMetrics.StyleSimulations & StyleSimulations.BoldSimulation) != 0;
            }
        }
 
        /// <summary>
        /// Obtain a glyph typeface that corresponds to the Typeface object constructed from an OpenType font family.
        /// If the Typeface was constructed from a composite font family, returns null.
        /// </summary>
        /// <param name="glyphTypeface">GlyphTypeface object that corresponds to this Typeface, or null if the Typeface
        /// was constructed from a composite font.</param>
        /// <returns>Whether glyphTypeface is not null.</returns>
        public bool TryGetGlyphTypeface(out GlyphTypeface glyphTypeface)
        {
            glyphTypeface = CachedTypeface.TypefaceMetrics as GlyphTypeface;
            return glyphTypeface != null;
        }
 
 
        /// <summary>
        /// Fallback font family
        /// </summary>
        internal FontFamily FallbackFontFamily
        {
            get { return _fallbackFontFamily; }
        }
 
        /// <summary>
        /// (Western) x-height relative to em size.
        /// </summary>
        public double XHeight
        {
            get 
            { 
                return CachedTypeface.TypefaceMetrics.XHeight;
            }
        }
 
 
        /// <summary>
        /// Distance from baseline to top of English ----, relative to em size.
        /// </summary>
        public double CapsHeight
        {
            get 
            { 
                return CachedTypeface.TypefaceMetrics.CapsHeight;
            }
        }
 
 
        /// <summary>
        /// Distance from baseline to underline position
        /// </summary>
        public double UnderlinePosition
        {
            get 
            { 
                return CachedTypeface.TypefaceMetrics.UnderlinePosition;
            }
        }
 
 
        /// <summary>
        /// Underline thickness
        /// </summary>
        public double UnderlineThickness
        {
            get
            {
                return CachedTypeface.TypefaceMetrics.UnderlineThickness;
            }
        }
 
 
        /// <summary>
        /// Distance from baseline to strike-through position
        /// </summary>
        public double StrikethroughPosition
        {
            get 
            { 
                return CachedTypeface.TypefaceMetrics.StrikethroughPosition;
            }
        }
 
 
        /// <summary>
        /// strike-through thickness
        /// </summary>
        public double StrikethroughThickness
        {
            get
            {
                return CachedTypeface.TypefaceMetrics.StrikethroughThickness;
            }
        }
 
        /// <summary>
        /// Collection of culture-dependant face names.
        /// </summary>
        public LanguageSpecificStringDictionary FaceNames
        {
            get
            {
                return new LanguageSpecificStringDictionary(CachedTypeface.TypefaceMetrics.AdjustedFaceNames);
            }
        }
 
        internal double Baseline(double emSize, double toReal, double pixelsPerDip, TextFormattingMode textFormattingMode)
        {
            return CachedTypeface.FirstFontFamily.Baseline(emSize, toReal, pixelsPerDip, textFormattingMode);            
        }
 
        internal double LineSpacing(double emSize, double toReal, double pixelsPerDip, TextFormattingMode textFormattingMode)
        {
            return CachedTypeface.FirstFontFamily.LineSpacing(emSize, toReal, pixelsPerDip, textFormattingMode);
        }
 
        /// <summary>
        /// Flag indicating if the typeface is of symbol type
        /// </summary>
        internal bool Symbol
        {
            get
            {
                return CachedTypeface.TypefaceMetrics.Symbol;
            }
        }        
 
        internal bool NullFont
        {
            get 
            {
                return CachedTypeface.NullFont;
            }
        }
 
        // Tries to get a GlyphTypeface based on the Typeface properties. The 
        // return value can be null. However, if CheckFastPathNominalGlyphs
        // returns true, then one can expect this function to return a valid
        // GlyphTypeface that maps all the specified text.
        internal GlyphTypeface TryGetGlyphTypeface()
        {
            return CachedTypeface.TypefaceMetrics as GlyphTypeface;
        }
 
        internal FontStyle CanonicalStyle
        {
            get 
            {
                return CachedTypeface.CanonicalStyle;
            }
        }
 
        internal FontWeight CanonicalWeight
        {
            get 
            {
                return CachedTypeface.CanonicalWeight;
            }
        }
 
        internal FontStretch CanonicalStretch
        {
            get 
            {
                return CachedTypeface.CanonicalStretch;
            }
        }
        
       
        /// <summary>
        /// Scan through specified character string checking for valid character 
        /// nominal glyph.
        /// </summary>
        /// <param name="charBufferRange">character buffer range</param>
        /// <param name="emSize">height of Em</param>
        /// <param name="scalingFactor">This is the factor by which we will scale up 
        /// the metrics. Typically this value to used to convert metrics from the real 
        /// space to the ideal space</param>
        /// <param name="widthMax">maximum width allowed</param>
        /// <param name="keepAWord">do not stop arbitrarily in the middle of a word</param>
        /// <param name="numberSubstitution">digits require complex shaping</param>
        /// <param name="cultureInfo">CultureInfo of the text</param>
        /// <param name="textFormattingMode">The TextFormattingMode used (Ideal vs. Display)</param>
        /// <param name="isSideways">Indicates whether to rotate glyphs.</param>
        /// <param name="breakOnTabs">Determines whether to stop checking at a tab and 
        /// break the run there</param>
        /// <param name="stringLengthFit">number of character fit in given width</param>
        /// <returns>whether the specified string can be optimized by nominal glyph lookup</returns>
        internal bool CheckFastPathNominalGlyphs(
            CharacterBufferRange    charBufferRange,
            double                  emSize,
            float                   pixelsPerDip,
            double                  scalingFactor,
            double                  widthMax,
            bool                    keepAWord,
            bool                    numberSubstitution,
            CultureInfo             cultureInfo,
            TextFormattingMode      textFormattingMode,
            bool                    isSideways,
            bool                    breakOnTabs,
            out int                 stringLengthFit
            )
        {
            stringLengthFit = 0;
 
            if (CachedTypeface.NullFont) return false;
 
            GlyphTypeface glyphTypeface = TryGetGlyphTypeface();
            
            if (glyphTypeface == null) return false;
 
            double totalWidth = 0;
            int i = 0;
 
            ushort blankGlyph = glyphTypeface.BlankGlyphIndex;
            ushort glyph = blankGlyph;
 
            ushort charFlagsMask = numberSubstitution ?
                (ushort)(CharacterAttributeFlags.CharacterComplex | CharacterAttributeFlags.CharacterDigit) :
                (ushort)CharacterAttributeFlags.CharacterComplex;
            ushort charFlags = 0;
            ushort charFastTextCheck = (ushort)(CharacterAttributeFlags.CharacterFastText | CharacterAttributeFlags.CharacterIdeo);
 
            bool symbolTypeface = glyphTypeface.Symbol;
            if (symbolTypeface)
            {
                // we don't care what code points are present if it's a non-Unicode font such as Symbol or Wingdings;
                // the code points don't have any standardized meanings, and we always want to bypass shaping
                charFlagsMask = 0;
            }
 
            bool ignoreWidths = widthMax == double.MaxValue;
            ushort[] glyphIndices = BufferCache.GetUShorts(charBufferRange.Length);
            MS.Internal.Text.TextInterface.GlyphMetrics[] glyphMetrics = ignoreWidths ? null : BufferCache.GetGlyphMetrics(charBufferRange.Length);
 
            glyphTypeface.GetGlyphMetricsOptimized(charBufferRange, 
                                                   emSize,
                                                   pixelsPerDip,
                                                   glyphIndices,
                                                   glyphMetrics,
                                                   textFormattingMode,
                                                   isSideways
                                                   );
 
            double designToEm = emSize / glyphTypeface.DesignEmHeight;
 
            //
            // This block will advance until one of:
            // 1. The end of the charBufferRange is reached
            // 2. The charFlags have some of the charFlagsMask values
            // 3. The glyph is BlankGlyph (space)
            // 4. Glyph index is 0 (unless symbol font)
            //
            // At this point totalWidth includes all of the widths including the stop character (which fits above)
            // i indexes the next character (not included in the width)
            //
            if(keepAWord)
            {
                do
                {
                    char ch = charBufferRange[i++];
                    if (ch == TextStore.CharLineFeed || ch == TextStore.CharCarriageReturn || (breakOnTabs && ch == TextStore.CharTab))
                    {
                        --i;
                        break;
                    }
                    else
                    {
                        int charClass = (int)Classification.GetUnicodeClassUTF16(ch);
                        charFlags = Classification.CharAttributeOf(charClass).Flags;
                        charFastTextCheck &= charFlags;
 
                        glyph = glyphIndices[i-1];
                        if (!ignoreWidths)
                        {
                            totalWidth += TextFormatterImp.RoundDip(glyphMetrics[i - 1].AdvanceWidth * designToEm, pixelsPerDip, textFormattingMode) * scalingFactor;
                        }
                    }
                } while(
                        i < charBufferRange.Length
                    &&  ((charFlags & charFlagsMask) == 0)
                    &&  (glyph != 0 || symbolTypeface)
                    &&  glyph != blankGlyph
                    );
 
                // i is now at a character immediately following a leading blank
            }
 
            //
            // This block will advance until one of:
            // 1. The end of the charBufferRange is reached
            // 2. The charFlags have some of the charFlagsMask values
            // 3. Glyph index is 0 (unless symbol font)
            // 4. totalWidth > widthMax
            //
            
            while(
                    i < charBufferRange.Length
                &&  (ignoreWidths || totalWidth <= widthMax)
                &&  ((charFlags & charFlagsMask) == 0)
                &&  (glyph != 0 || symbolTypeface)
                )
            {
                char ch = charBufferRange[i++];
                if (ch == TextStore.CharLineFeed || ch == TextStore.CharCarriageReturn || (breakOnTabs && ch == TextStore.CharTab))
                {
                    --i;
                    break;
                }
                else
                {
                    int charClass = (int)Classification.GetUnicodeClassUTF16(ch);
                    charFlags = Classification.CharAttributeOf(charClass).Flags;
                    charFastTextCheck &= charFlags;
 
                    glyph = glyphIndices[i-1];
                    if (!ignoreWidths)
                    {
                        totalWidth += TextFormatterImp.RoundDip(glyphMetrics[i - 1].AdvanceWidth * designToEm, pixelsPerDip, textFormattingMode) * scalingFactor;
                    }
                }
            }
 
            BufferCache.ReleaseUShorts(glyphIndices);
            glyphIndices = null;
            BufferCache.ReleaseGlyphMetrics(glyphMetrics);
            glyphMetrics = null;
 
            if (symbolTypeface)
            {
                // always optimize for non-Unicode font as we don't support shaping or typographic features;
                // we also don't fall back from non-Unicode fonts so we don't care if there are missing glyphs
                stringLengthFit = i;
                return true;
            }
 
            if (glyph == 0)
            {
                // character is not supported by the font
                return false;
            }
 
            if ((charFlags & charFlagsMask) != 0)
            {
                // complex character encountered, exclude it
                Debug.Assert(i > 0);
 
                if(--i <= 0)
                {
                    // first char is complex, fail the call
                    return false;
                }
            }
 
            stringLengthFit = i;
            TypographyAvailabilities typography = glyphTypeface.FontFaceLayoutInfo.TypographyAvailabilities;
 
            if ((charFastTextCheck & (byte) CharacterAttributeFlags.CharacterFastText) != 0)
            {
                // all input code points are Fast Text
                if ((typography &
                         (  TypographyAvailabilities.FastTextTypographyAvailable
                          | TypographyAvailabilities.FastTextMajorLanguageLocalizedFormAvailable
                         )
                     ) != 0
                   )
                {
                    // Considered too risky to optimize. It is either because the font
                    // has required features or the font has 'locl' lookup for major languages.
                    return false;
                }
                else if ((typography & TypographyAvailabilities.FastTextExtraLanguageLocalizedFormAvailable) != 0)
                {
                    // The font has 'locl' lookup for FastText code points for non major languages.
                    // Check whether the input is major langauge. If it is, we are still good to optimize.
                    return MajorLanguages.Contains(cultureInfo);
                }
                else
                {
                    // No FastText flags are present, safe to optimize
                    return true;
                }
            }
            else if ((charFastTextCheck & (byte) CharacterAttributeFlags.CharacterIdeo) != 0)
            {
                // The input are all ideographs, check the IdeoTypographyAvailable bit. It is safe if
                // the bit is not set.
                return ((typography & TypographyAvailabilities.IdeoTypographyAvailable) == 0);
            }
            else
            {
                // for all the rest of the cases, just check whether there is any required typography
                // present at all. If none exists, it is optimizable. We might under-optimize here but
                // it will be non-major languages.
                return ((typography & TypographyAvailabilities.Available) == 0);
            }            
        }
 
        /// <summary>
        /// Lookup characters nominal glyphs and width
        /// </summary>
        /// <param name="charBufferRange">character buffer range</param>
        /// <param name="emSize">height of Em</param>
        /// <param name="toIdeal"> scaling factor from real to ideal unit </param>
        /// <param name="nominalWidths">glyph nominal advances in ideal units</param>
        /// <returns>true for success</returns>
        /// <remarks>This function is only used in fast path, and can only be called 
        /// if CheckFastPathNominalGlyphs has previously returned true.</remarks>
        internal void GetCharacterNominalWidthsAndIdealWidth(
            CharacterBufferRange charBufferRange,
            double               emSize,
            float                pixelsPerDip,
            double               toIdeal,
            TextFormattingMode   textFormattingMode,
            bool                 isSideways,
            out int[]            nominalWidths
            )
        {
            int idealWidth;
            GetCharacterNominalWidthsAndIdealWidth(
                                charBufferRange,
                                emSize,
                                pixelsPerDip,
                                toIdeal,
                                textFormattingMode,
                                isSideways,
                                out nominalWidths,
                                out idealWidth
                                );
        }
 
        /// <summary>
        /// Lookup characters nominal glyphs and width
        /// </summary>
        /// <param name="charBufferRange">character buffer range</param>
        /// <param name="emSize">height of Em</param>
        /// <param name="toIdeal"> scaling factor from real to ideal unit </param>
        /// <param name="nominalWidths">glyph nominal advances in ideal units</param>
        /// <param name="idealWidth">total width in ideal units</param>
        /// <returns>true for success</returns>
        /// <remarks>This function is only used in fast path, and can only be called 
        /// if CheckFastPathNominalGlyphs has previously returned true.</remarks>
        internal void GetCharacterNominalWidthsAndIdealWidth(
            CharacterBufferRange charBufferRange,
            double               emSize,
            float                pixelsPerDip,
            double               toIdeal,
            TextFormattingMode   textFormattingMode,
            bool                 isSideways,
            out int[]            nominalWidths,
            out int              idealWidth
            )
        {
            // This function should only be called if CheckFastPathNominalGlyphs has
            // returned true so we can assume the ITypefaceMetrics is a GlyphTypeface.
            GlyphTypeface glyphTypeface = TryGetGlyphTypeface();
            Invariant.Assert(glyphTypeface != null);
 
            MS.Internal.Text.TextInterface.GlyphMetrics[] glyphMetrics = BufferCache.GetGlyphMetrics(charBufferRange.Length);
 
            glyphTypeface.GetGlyphMetricsOptimized(charBufferRange, 
                                                   emSize,
                                                   pixelsPerDip,
                                                   textFormattingMode,
                                                   isSideways,
                                                   glyphMetrics);
 
            nominalWidths = new int[charBufferRange.Length];
            idealWidth = 0;
 
            if (TextFormattingMode.Display == textFormattingMode)
            {
                double designToEm = emSize / glyphTypeface.DesignEmHeight;
                for (int i = 0; i < charBufferRange.Length; i++)
                {
                    nominalWidths[i] = (int)Math.Round(TextFormatterImp.RoundDipForDisplayMode(glyphMetrics[i].AdvanceWidth * designToEm, pixelsPerDip) * toIdeal);
                    idealWidth += nominalWidths[i];
                }
            }
            else
            {
                double designToEm = emSize * toIdeal / glyphTypeface.DesignEmHeight;
                for (int i = 0; i < charBufferRange.Length; i++)
                {
                    nominalWidths[i] = (int)Math.Round(glyphMetrics[i].AdvanceWidth * designToEm);
                    idealWidth += nominalWidths[i];
                }
            }
 
            BufferCache.ReleaseGlyphMetrics(glyphMetrics);
        }
 
        /// <summary>
        /// Create correspondent hash code for the object
        /// </summary>
        /// <returns>object hash code</returns>
        public override int GetHashCode()
        {
            int hash = _fontFamily.GetHashCode();
 
            if (_fallbackFontFamily != null)
                hash = HashFn.HashMultiply(hash) + _fallbackFontFamily.GetHashCode();
 
            hash = HashFn.HashMultiply(hash) + _style.GetHashCode();
            hash = HashFn.HashMultiply(hash) + _weight.GetHashCode();
            hash = HashFn.HashMultiply(hash) + _stretch.GetHashCode();
            return HashFn.HashScramble(hash);
        }
 
 
 
        /// <summary>
        /// Equality check
        /// </summary>
        public override bool Equals(object o)
        {
            Typeface t = o as Typeface;
            if(t == null)
                return false;
 
            return  _style == t._style 
                &&  _weight == t._weight 
                &&  _stretch == t._stretch 
                &&  _fontFamily.Equals(t._fontFamily)
                &&  CompareFallbackFontFamily(t._fallbackFontFamily);
        }
 
 
        internal bool CompareFallbackFontFamily(FontFamily fallbackFontFamily)
        {
            if (fallbackFontFamily == null || _fallbackFontFamily == null)
                return fallbackFontFamily == _fallbackFontFamily;
 
            return _fallbackFontFamily.Equals(fallbackFontFamily);
        }
 
        //----------------------------------------
        // Private method
        //----------------------------------------
        private CachedTypeface CachedTypeface
        {
            get 
            {
                if (_cachedTypeface == null)
                {
                    CachedTypeface cachedTypeface = TypefaceMetricsCache.ReadonlyLookup(this) as CachedTypeface;
                    
                    if (cachedTypeface == null)
                    {
                       cachedTypeface = ConstructCachedTypeface();
                       TypefaceMetricsCache.Add(this, cachedTypeface);
                    }
 
                    // For thread-safety, set the _cachedTypeface field only after we have a fully 
                    // initialized CachedTypeface object.
                    _cachedTypeface = cachedTypeface;
                }
 
                return _cachedTypeface;
            }
        }        
 
        private CachedTypeface ConstructCachedTypeface()
        {
            FontStyle canonicalStyle     = _style;
            FontWeight canonicalWeight   = _weight;
            FontStretch canonicalStretch = _stretch;
 
            //
            // We always call FontFamily.FindFirstFontFamilyAndFace() method to resolve the 
            // canonical styles since the implied styles in FontFamily name will override 
            // the given styles in the Typeface. But we don't always use the IFontFamily 
            // instance returned from this method because an equal instance might already be 
            // cached. 
            //
            FontFamily sourceFontFamily = FontFamily;
            
            IFontFamily firstFontFamily = sourceFontFamily.FindFirstFontFamilyAndFace(
                ref canonicalStyle,
                ref canonicalWeight,
                ref canonicalStretch
                );
 
            if (firstFontFamily == null)
            {
                if (FallbackFontFamily != null)
                {
                    sourceFontFamily = FallbackFontFamily;
                    firstFontFamily = sourceFontFamily.FindFirstFontFamilyAndFace(
                        ref canonicalStyle,
                        ref canonicalWeight,
                        ref canonicalStretch
                        );                  
                }
 
                if (firstFontFamily == null)
                {
                    sourceFontFamily = null;
                    firstFontFamily = FontFamily.LookupFontFamily(FontFamily.NullFontFamilyCanonicalName);
                }                
            }
 
            // If it's a named font, map all occurrences of the same name to one cached IFontFamily.
            if (sourceFontFamily != null && sourceFontFamily.Source != null)
            {
                // We lookup in the cache to see if there is cached IFontFamily instance of the source FontFamily. Otherwise, 
                // this IFontFamily value is added to the TypefaceMetrics cache. 
                IFontFamily cachedValue = TypefaceMetricsCache.ReadonlyLookup(sourceFontFamily.FamilyIdentifier) as IFontFamily;
                
                if (cachedValue != null)
                {
                    firstFontFamily = cachedValue;
                }
                else
                {
                    TypefaceMetricsCache.Add(sourceFontFamily.FamilyIdentifier, firstFontFamily);   
                }                
            }
 
            ITypefaceMetrics typefaceMetrics = firstFontFamily.GetTypefaceMetrics(canonicalStyle, canonicalWeight, canonicalStretch);            
 
            return new CachedTypeface(
                canonicalStyle,
                canonicalWeight,
                canonicalStretch,
                firstFontFamily,
                typefaceMetrics,
                sourceFontFamily == null
                );
        }
    }
}