File: MS\Internal\TextFormatting\TextFormatterImp.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:  Text formatter implementation
//
//
 
 
using System;
using System.Security;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Media.TextFormatting;
using MS.Utility;
 
using SR=MS.Internal.PresentationCore.SR;
using MS.Internal.Shaping;
using MS.Internal.Text.TextInterface;
using MS.Internal.FontCache;
 
#if !OPTIMALBREAK_API
using MS.Internal.PresentationCore;
#endif
 
 
namespace MS.Internal.TextFormatting
{
    /// <summary>
    /// Implementation of TextFormatter
    /// </summary>
    internal sealed class TextFormatterImp : TextFormatter
    {
        private FrugalStructList<TextFormatterContext>  _contextList;               // LS context free list
        private bool                                    _multipleContextProhibited; // prohibit multiple contexts within the same formatter
        private GlyphingCache                           _glyphingCache;             // Glyphing cache for font linking process
        private TextFormattingMode                      _textFormattingMode;
        private TextAnalyzer                            _textAnalyzer;              // TextAnalyzer used for shaping process
 
        private const int MaxGlyphingCacheCapacity = 16;
 
        /// <summary>
        /// Construct an instance of TextFormatter implementation
        /// </summary>
        internal TextFormatterImp(TextFormattingMode textFormattingMode)
            : this(null, textFormattingMode)
        { }
 
        /// <summary>
        /// Construct an instance of TextFormatter implementation
        /// </summary>
        internal TextFormatterImp() : this(null, TextFormattingMode.Ideal)
        {}
 
        /// <summary>
        /// Construct an instance of TextFormatter implementation with the specified context
        /// </summary>
        /// <param name="soleContext"></param>
        /// <remarks>
        /// TextFormatter created via this special ctor takes a specified context and uses it as the only known
        /// context within its entire lifetime. It prohibits reentering of TextFormatter during formatting as only
        /// one context is allowed. This restriction is critical to the optimal break algorithm supported by the current
        /// version of PTLS.
        /// </remarks>
        internal TextFormatterImp(TextFormatterContext soleContext, TextFormattingMode textFormattingMode)
        {
            _textFormattingMode = textFormattingMode;
 
            if (soleContext != null)
                _contextList.Add(soleContext);
 
            _multipleContextProhibited = (_contextList.Count != 0);
        }
 
 
        /// <summary>
        /// Finalizing text formatter
        /// </summary>
        ~TextFormatterImp()
        {
            CleanupInternal();
        }
 
 
        /// <summary>
        /// Release all unmanaged LS contexts
        /// </summary>
        public override void Dispose()
        {
            CleanupInternal();
            base.Dispose();
            GC.SuppressFinalize(this);
        }
 
 
        /// <summary>
        /// Release all unmanaged LS contexts
        /// </summary>
        private void CleanupInternal()
        {
            for (int i = 0; i < _contextList.Count; i++)
            {
                _contextList[i].Destroy();
            }
            _contextList.Clear();
        }
 
 
        /// <summary>
        /// Client to format a text line that fills a paragraph in the document.
        /// </summary>
        /// <param name="textSource">an object representing text layout clients text source for TextFormatter.</param>
        /// <param name="firstCharIndex">character index to specify where in the source text the line starts</param>
        /// <param name="paragraphWidth">width of paragraph in which the line fills</param>
        /// <param name="paragraphProperties">properties that can change from one paragraph to the next, such as text flow direction, text alignment, or indentation.</param>
        /// <param name="previousLineBreak">LineBreak property of the previous text line, or null if this is the first line in the paragraph</param>
        /// <returns>object representing a line of text that client interacts with. </returns>
        public override TextLine FormatLine(
            TextSource                  textSource,
            int                         firstCharIndex,
            double                      paragraphWidth,
            TextParagraphProperties     paragraphProperties,
            TextLineBreak               previousLineBreak
            )
        {
            return FormatLineInternal(
                textSource,
                firstCharIndex,
                0,   // lineLength
                paragraphWidth,
                paragraphProperties,
                previousLineBreak,
                new TextRunCache()  // local cache, only live within this call
                );
        }
 
 
 
        /// <summary>
        /// Client to format a text line that fills a paragraph in the document.
        /// </summary>
        /// <param name="textSource">an object representing text layout clients text source for TextFormatter.</param>
        /// <param name="firstCharIndex">character index to specify where in the source text the line starts</param>
        /// <param name="paragraphWidth">width of paragraph in which the line fills</param>
        /// <param name="paragraphProperties">properties that can change from one paragraph to the next, such as text flow direction, text alignment, or indentation.</param>
        /// <param name="previousLineBreak">LineBreak property of the previous text line, or null if this is the first line in the paragraph</param>
        /// <param name="textRunCache">an object representing content cache of the client.</param>
        /// <returns>object representing a line of text that client interacts with. </returns>
        public override TextLine FormatLine(
            TextSource                  textSource,
            int                         firstCharIndex,
            double                      paragraphWidth,
            TextParagraphProperties     paragraphProperties,
            TextLineBreak               previousLineBreak,
            TextRunCache                textRunCache
            )
        {
            return FormatLineInternal(
                textSource,
                firstCharIndex,
                0,   // lineLength
                paragraphWidth,
                paragraphProperties,
                previousLineBreak,
                textRunCache
                );
        }
 
 
 
        /// <summary>
        /// Client to reconstruct a previously formatted text line
        /// </summary>
        /// <param name="textSource">an object representing text layout clients text source for TextFormatter.</param>
        /// <param name="firstCharIndex">character index to specify where in the source text the line starts</param>
        /// <param name="lineLength">character length of the line</param>
        /// <param name="paragraphWidth">width of paragraph in which the line fills</param>
        /// <param name="paragraphProperties">properties that can change from one paragraph to the next, such as text flow direction, text alignment, or indentation.</param>
        /// <param name="previousLineBreak">LineBreak property of the previous text line, or null if this is the first line in the paragraph</param>
        /// <param name="textRunCache">an object representing content cache of the client.</param>
        /// <returns>object representing a line of text that client interacts with. </returns>
#if OPTIMALBREAK_API
        public override TextLine RecreateLine(
#else
        internal override TextLine RecreateLine(
#endif
            TextSource                  textSource,
            int                         firstCharIndex,
            int                         lineLength,
            double                      paragraphWidth,
            TextParagraphProperties     paragraphProperties,
            TextLineBreak               previousLineBreak,
            TextRunCache                textRunCache
            )
        {
            return FormatLineInternal(
                textSource,
                firstCharIndex,
                lineLength,
                paragraphWidth,
                paragraphProperties,
                previousLineBreak,
                textRunCache
                );
        }
 
 
 
        /// <summary>
        /// Format and produce a text line either with or without previously known
        /// line break point.
        /// </summary>
        private TextLine FormatLineInternal(
            TextSource                  textSource,
            int                         firstCharIndex,
            int                         lineLength,
            double                      paragraphWidth,
            TextParagraphProperties     paragraphProperties,
            TextLineBreak               previousLineBreak,
            TextRunCache                textRunCache
            )
        {
            EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordText, EventTrace.Level.Verbose, EventTrace.Event.WClientStringBegin, "TextFormatterImp.FormatLineInternal Start");
 
            // prepare formatting settings
            FormatSettings settings = PrepareFormatSettings(
                textSource,
                firstCharIndex,
                paragraphWidth,
                paragraphProperties,
                previousLineBreak,
                textRunCache,
                (lineLength != 0),  // Do optimal break if break is given
                true,    // isSingleLineFormatting
                _textFormattingMode
                );
 
            TextLine textLine = null;
 
            if (    !settings.Pap.AlwaysCollapsible
                &&  previousLineBreak == null
                &&  lineLength <= 0
                )
            {
                // simple text line.
                textLine = SimpleTextLine.Create(
                    settings,
                    firstCharIndex,
                    RealToIdealFloor(paragraphWidth),
                    textSource.PixelsPerDip
                    ) as TextLine;
            }
 
            if (textLine == null)
            {
                // content is complex, creating complex line
                textLine = new TextMetrics.FullTextLine(
                    settings,
                    firstCharIndex,
                    lineLength,
                    RealToIdealFloor(paragraphWidth),
                    LineFlags.None
                    ) as TextLine;
            }
 
            EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordText, EventTrace.Level.Verbose, EventTrace.Event.WClientStringEnd, "TextFormatterImp.FormatLineInternal End");
 
            return textLine;
        }
 
 
 
        /// <summary>
        /// Client to ask for the possible smallest and largest paragraph width that can fully contain the passing text content
        /// </summary>
        /// <param name="textSource">an object representing text layout clients text source for TextFormatter.</param>
        /// <param name="firstCharIndex">character index to specify where in the source text the line starts</param>
        /// <param name="paragraphProperties">properties that can change from one paragraph to the next, such as text flow direction, text alignment, or indentation.</param>
        /// <returns>min max paragraph width</returns>
        public override MinMaxParagraphWidth FormatMinMaxParagraphWidth(
            TextSource                  textSource,
            int                         firstCharIndex,
            TextParagraphProperties     paragraphProperties
            )
        {
            return FormatMinMaxParagraphWidth(
                textSource,
                firstCharIndex,
                paragraphProperties,
                new TextRunCache()  // local cache, only live within this call
                );
        }
 
 
 
        /// <summary>
        /// Client to ask for the possible smallest and largest paragraph width that can fully contain the passing text content
        /// </summary>
        /// <param name="textSource">an object representing text layout clients text source for TextFormatter.</param>
        /// <param name="firstCharIndex">character index to specify where in the source text the line starts</param>
        /// <param name="paragraphProperties">properties that can change from one paragraph to the next, such as text flow direction, text alignment, or indentation.</param>
        /// <param name="textRunCache">an object representing content cache of the client.</param>
        /// <returns>min max paragraph width</returns>
        public override MinMaxParagraphWidth FormatMinMaxParagraphWidth(
            TextSource                  textSource,
            int                         firstCharIndex,
            TextParagraphProperties     paragraphProperties,
            TextRunCache                textRunCache
            )
        {
            // prepare formatting settings
            FormatSettings settings = PrepareFormatSettings(
                textSource,
                firstCharIndex,
                0,      // infinite paragraphWidth
                paragraphProperties,
                null,   // always format the whole paragraph - no previousLineBreak
                textRunCache,
                false,  // optimalBreak
                true,   // isSingleLineFormatting
                _textFormattingMode
                );
 
            // create specialized line specifically for min/max calculation
            TextMetrics.FullTextLine line = new TextMetrics.FullTextLine(
                settings,
                firstCharIndex,
                0,  // lineLength
                0,  // paragraph width has no significant meaning in min/max calculation
                (LineFlags.KeepState | LineFlags.MinMax)
                );
 
            // line width in this case is the width of a line when the entire paragraph is laid out
            // as a single long line.
            MinMaxParagraphWidth minMax = new MinMaxParagraphWidth(line.MinWidth, line.Width);
            line.Dispose();
            return minMax;
        }
 
        internal TextFormattingMode TextFormattingMode
        {
            get
            {
                return _textFormattingMode;
            }
        }
 
        /// <summary>
        /// Client to cache information about a paragraph to be used during optimal paragraph line formatting
        /// </summary>
        /// <param name="textSource">an object representing text layout clients text source for TextFormatter.</param>
        /// <param name="firstCharIndex">character index to specify where in the source text the line starts</param>
        /// <param name="paragraphWidth">width of paragraph in which the line fills</param>
        /// <param name="paragraphProperties">properties that can change from one paragraph to the next, such as text flow direction, text alignment, or indentation.</param>
        /// <param name="previousLineBreak">text formatting state at the point where the previous line in the paragraph
        /// was broken by the text formatting process, as specified by the TextLine.LineBreak property for the previous
        /// line; this parameter can be null, and will always be null for the first line in a paragraph.</param>
        /// <param name="textRunCache">an object representing content cache of the client.</param>
        /// <returns>object representing a line of text that client interacts with. </returns>
#if OPTIMALBREAK_API
        public override TextParagraphCache CreateParagraphCache(
#else
        internal override TextParagraphCache CreateParagraphCache(
#endif
            TextSource                  textSource,
            int                         firstCharIndex,
            double                      paragraphWidth,
            TextParagraphProperties     paragraphProperties,
            TextLineBreak               previousLineBreak,
            TextRunCache                textRunCache
            )
        {
            // prepare formatting settings
            FormatSettings settings = PrepareFormatSettings(
                textSource,
                firstCharIndex,
                paragraphWidth,
                paragraphProperties,
                previousLineBreak,
                textRunCache,
                true,   // optimalBreak
                false,  // !isSingleLineFormatting
                _textFormattingMode
                );
 
            //
            // Optimal paragraph formatting session specific check
            //
            if (!settings.Pap.Wrap && settings.Pap.OptimalBreak)
            {
                // Optimal paragraph must wrap.
                throw new ArgumentException(SR.OptimalParagraphMustWrap);
            }
 
            // create paragraph content cache object
            return new TextParagraphCache(
                settings,
                firstCharIndex,
                RealToIdeal(paragraphWidth)
                );
        }
 
 
 
        /// <summary>
        /// Validate all the relevant text formatting initial settings and package them
        /// </summary>
        private FormatSettings PrepareFormatSettings(
            TextSource                  textSource,
            int                         firstCharIndex,
            double                      paragraphWidth,
            TextParagraphProperties     paragraphProperties,
            TextLineBreak               previousLineBreak,
            TextRunCache                textRunCache,
            bool                        useOptimalBreak,
            bool                        isSingleLineFormatting,
            TextFormattingMode              textFormattingMode
            )
        {
            VerifyTextFormattingArguments(
                textSource,
                firstCharIndex,
                paragraphWidth,
                paragraphProperties,
                textRunCache
                );
 
            if (textRunCache.Imp == null)
            {
                // No run cache object available, create one
                textRunCache.Imp = new TextRunCacheImp();
            }
 
            // initialize formatting settings
            return new FormatSettings(
                this,
                textSource,
                textRunCache.Imp,
                new ParaProp(this, paragraphProperties, useOptimalBreak),
                previousLineBreak,
                isSingleLineFormatting,
                textFormattingMode,
                false
                );
        }
 
 
 
        /// <summary>
        /// Verify all text formatting arguments
        /// </summary>
        private void VerifyTextFormattingArguments(
            TextSource                  textSource,
            int                         firstCharIndex,
            double                      paragraphWidth,
            TextParagraphProperties     paragraphProperties,
            TextRunCache                textRunCache
            )
        {
            ArgumentNullException.ThrowIfNull(textSource);
 
            ArgumentNullException.ThrowIfNull(textRunCache);
 
            ArgumentNullException.ThrowIfNull(paragraphProperties);
 
            if (paragraphProperties.DefaultTextRunProperties == null)
                throw new ArgumentNullException("paragraphProperties.DefaultTextRunProperties");
 
            if (paragraphProperties.DefaultTextRunProperties.Typeface == null)
                throw new ArgumentNullException("paragraphProperties.DefaultTextRunProperties.Typeface");
 
            ArgumentOutOfRangeException.ThrowIfEqual(paragraphWidth, double.NaN);
            ArgumentOutOfRangeException.ThrowIfNegative(paragraphWidth);
            ArgumentOutOfRangeException.ThrowIfEqual(paragraphWidth, double.PositiveInfinity);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(paragraphWidth, Constants.RealInfiniteWidth);
 
            double realMaxFontRenderingEmSize = Constants.RealInfiniteWidth / Constants.GreatestMutiplierOfEm;
 
            ArgumentOutOfRangeException.ThrowIfNegative(paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize, "paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize");
            ArgumentOutOfRangeException.ThrowIfGreaterThan(paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize, realMaxFontRenderingEmSize, "paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize");
            ArgumentOutOfRangeException.ThrowIfGreaterThan(paragraphProperties.Indent, Constants.RealInfiniteWidth, "paragraphProperties.Indent");
            ArgumentOutOfRangeException.ThrowIfGreaterThan(paragraphProperties.LineHeight, Constants.RealInfiniteWidth, "paragraphProperties.LineHeight");
            ArgumentOutOfRangeException.ThrowIfNegative(paragraphProperties.DefaultIncrementalTab, "paragraphProperties.DefaultIncrementalTab");
            ArgumentOutOfRangeException.ThrowIfGreaterThan(paragraphProperties.DefaultIncrementalTab, Constants.RealInfiniteWidth, "paragraphProperties.DefaultIncrementalTab");
        }
 
 
        /// <summary>
        /// Validate the input character hit
        /// </summary>
        internal static void VerifyCaretCharacterHit(
            CharacterHit    characterHit,
            int             cpFirst,
            int             cchLength
            )
        {
            ArgumentOutOfRangeException.ThrowIfGreaterThan(cpFirst, characterHit.FirstCharacterIndex);
            ArgumentOutOfRangeException.ThrowIfLessThan(cpFirst, characterHit.FirstCharacterIndex - cchLength);
 
            ArgumentOutOfRangeException.ThrowIfNegative(characterHit.TrailingLength, nameof(cchLength));
        }
 
 
 
        /// <summary>
        /// Acquire a free TextFormatter context for complex line operation
        /// </summary>
        /// <param name="owner">object that becomes the owner of LS context once acquired</param>
        /// <param name="ploc">matching PLOC</param>
        /// <returns>Active LS context</returns>
        /// <SecurityNotes>
        /// Critical - this sets the owner of the context
        /// Safe     - this doesn't expose critical info
        /// </SecurityNotes>
        internal TextFormatterContext AcquireContext(
            object      owner,
            IntPtr      ploc
            )
        {
            Invariant.Assert(owner != null);
 
            TextFormatterContext context = null;
 
            int c;
            int contextCount = _contextList.Count;
 
            for (c = 0; c < contextCount; c++)
            {
                context = (TextFormatterContext)_contextList[c];
 
                if (ploc == IntPtr.Zero)
                {
                    if(context.Owner == null)
                        break;
                }
                else if (ploc == context.Ploc)
                {
                    // LS requires that we use the exact same context for line
                    // destruction or hittesting (part of the reason is that LS
                    // actually caches some run info in the context). So here
                    // we use the actual PLSC as the context signature so we
                    // locate the one we want.
 
                    Debug.Assert(context.Owner == null);
                    break;
                }
            }
 
            if (c == contextCount)
            {
                if (contextCount == 0 || !_multipleContextProhibited)
                {
                    //  no free one exists, create a new one
                    context = new TextFormatterContext();
                    _contextList.Add(context);
                }
                else
                {
                    // This instance of TextFormatter only allows a single context, reentering the
                    // same TextFormatter in this case is not allowed.
                    //
                    // This requirement is currently enforced only during optimal break computation.
                    // Client implementing nesting of optimal break content inside another must create
                    // a separate TextFormatter instance for each content in different nesting level.
                    throw new InvalidOperationException(SR.TextFormatterReentranceProhibited);
                }
            }
 
            Debug.Assert(context != null);
 
            context.Owner = owner;
            return context;
        }
 
 
        /// <summary>
        /// Create an anti-inversion transform from the inversion flags.
        /// The result is used to correct glyph bitmap on an output to
        /// a drawing surface with the specified inversions applied on.
        /// </summary>
        internal static MatrixTransform CreateAntiInversionTransform(
            InvertAxes  inversion,
            double      paragraphWidth,
            double      lineHeight
            )
        {
            if (inversion == InvertAxes.None)
            {
                // avoid creating unncessary pressure on GC when anti-transform is not needed.
                return null;
            }
 
            double m11 = 1;
            double m22 = 1;
            double offsetX = 0;
            double offsetY = 0;
 
            if ((inversion & InvertAxes.Horizontal) != 0)
            {
                m11 = -m11;
                offsetX = paragraphWidth;
            }
 
            if ((inversion & InvertAxes.Vertical) != 0)
            {
                m22 = -m22;
                offsetY = lineHeight;
            }
 
            return new MatrixTransform(m11, 0, 0, m22, offsetX, offsetY);
        }
 
        /// <summary>
        /// Compare text formatter real values - since values are rounded in Display mode, comparison
        /// must also round and only return true if one rounded value is greater than the other.
        /// </summary>
        /// <param name="x">First value to compare.</param>
        /// <param name="y">Second value to compare.</param>
        /// <param name="mode">Text formatting mode.</param>
        /// <returns>1 if x greater than y, -1 if x less than y, 0 if x == y</returns>
        internal static int CompareReal(double x, double y, double pixelsPerDip, TextFormattingMode mode)
        {
            double xDisplay = x;
            double yDisplay = y;
 
            if (mode == TextFormattingMode.Display)
            {
                xDisplay = RoundDipForDisplayMode(x, pixelsPerDip);
                yDisplay = RoundDipForDisplayMode(y, pixelsPerDip);
            }
 
            if (xDisplay > yDisplay)
            {
                return 1;
            }
 
            if (xDisplay < yDisplay)
            {
                return -1;
            }
 
            return 0;
        }
 
        internal static double RoundDip(double value, double pixelsPerDip, TextFormattingMode textFormattingMode)
        {
            if (TextFormattingMode.Display == textFormattingMode)
            {
                return RoundDipForDisplayMode(value, pixelsPerDip);
            }
            else
            {
                return value;
            }
        }
 
        internal static double RoundDipForDisplayMode(double value, double pixelsPerDip)
        {
            return RoundDipForDisplayMode(value, pixelsPerDip, MidpointRounding.ToEven);
        }
 
        private static double RoundDipForDisplayMode(double value, double pixelsPerDip, MidpointRounding midpointRounding)
        {
            return Math.Round(value * pixelsPerDip, midpointRounding) / pixelsPerDip;
        }
 
        /// <summary>
        /// The default behavior of Math.Round() leads to undesirable behavior
        /// When used for display mode justified text, where we can find 
        /// characters belonging to the same word jumping sideways.
        /// A word can break among several GlyphRuns. So we need consistent
        /// rounding of the width of the GlyphRuns. If the width of one GlyphRun
        /// rounds up and the next GlyphRun rounds down then we see characters 
        /// overlapping and so on.
        /// It is too late to change the behavior of our rounding universally
        /// so we are making the change targeted to Display mode + Justified text
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        internal static double RoundDipForDisplayModeJustifiedText(double value, double pixelsPerDip)
        {
            return RoundDipForDisplayMode(value, pixelsPerDip, MidpointRounding.AwayFromZero);
        }
 
        /// <summary>
        /// Scale LS ideal resolution value to real value
        /// </summary>
        internal static double IdealToRealWithNoRounding(double i)
        {
            return i * Constants.DefaultIdealToReal;
        }
 
        /// <summary>
        /// Scale LS ideal resolution value to real value
        /// </summary>
        internal double IdealToReal(double i, double pixelsPerDip)
        {
            double value = IdealToRealWithNoRounding(i);
            if (_textFormattingMode == TextFormattingMode.Display)
            {
                value = RoundDipForDisplayMode(value, pixelsPerDip);
            }
 
            if (i > 0)
            {
                // Non-zero values should not be converted to 0 accidentally through rounding, ensure that at least the min value is returned.
                value = Math.Max(value, Constants.DefaultIdealToReal);
            }
 
            return value;
        }
 
        /// <summary>
        /// Scale real value to LS ideal resolution
        /// </summary>
        internal static int RealToIdeal(double i)
        {
            int value = (int)Math.Round(i * ToIdeal);
            if (i > 0)
            {
                // Non-zero values should not be converted to 0 accidentally through rounding, ensure that at least the min value is returned.
                value = Math.Max(value, 1);
            }
            return value;
        }
 
        /// <summary>
        /// Scale the real value to LS ideal resolution
        /// Use the floor value of the scale value
        /// </summary>
        /// <remarks>
        /// Using Math.Round may result in a line larger than
        /// the actual given paragraph width. For example,
        /// round tripping 100.112 with factor 300 becomes 100.1133...
        /// Using floor to ensure we never go beyond paragraph width
        /// </remarks>
        internal static int RealToIdealFloor(double i)
        {
            int value = (int)Math.Floor(i * ToIdeal);
            if (i > 0)
            {
                // Non-zero values should not be converted to 0 accidentally through rounding, ensure that at least the min value is returned.
                value = Math.Max(value, 1);
            }
            return value;
        }
 
        /// <summary>
        /// Real to ideal value scaling factor
        /// </summary>
        internal static double ToIdeal
        {
            get { return Constants.DefaultRealToIdeal; }
        }
 
        /// <summary>
        /// Return the GlyphingCache associated with this TextFormatterImp object.
        /// GlyphingCache stores the mapping from Unicode scalar value to the physical font that is
        /// used to display it.
        /// </summary>
        internal GlyphingCache GlyphingCache
        {
            get
            {
                if (_glyphingCache == null)
                {
                    _glyphingCache = new GlyphingCache(MaxGlyphingCacheCapacity);
                }
 
                return _glyphingCache;
            }
        }
 
        /// <summary>
        /// Return the TextAnalyzer associated with this TextFormatterImp object.
        /// TextAnalyzer is used in shaping process.
        /// </summary>
        internal TextAnalyzer TextAnalyzer
        {
            get
            {
                if (_textAnalyzer == null)
                {
                    _textAnalyzer = DWriteFactory.Instance.CreateTextAnalyzer();
                }
 
                return _textAnalyzer;
            }
        }
    }
}