File: MS\Internal\TextFormatting\TextShapeableCharacters.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:  Implementation of text shapeable symbols for characters
//
//  Spec:      Text Formatting API.doc
//
//
 
 
using System.Windows.Markup;    // for XmlLanguage
using MS.Internal;
using MS.Internal.FontCache;
using MS.Internal.TextFormatting;
using MS.Internal.Shaping;
using MS.Internal.Text.TextInterface;
 
namespace System.Windows.Media.TextFormatting
{
    /// <summary>
    /// A specialized TextShapeableSymbols implemented by TextFormatter to represent
    /// a collection of glyphs from a physical typeface.
    /// </summary>
    internal sealed class TextShapeableCharacters : TextShapeableSymbols
    {
        private CharacterBufferRange    _characterBufferRange;
        private TextFormattingMode      _textFormattingMode;
        private bool                    _isSideways;
        private TextRunProperties       _properties;
        private double                  _emSize;    // after-scaled
 
        private ItemProps               _textItem;
        private ShapeTypeface           _shapeTypeface;
        private bool                    _nullShape;
 
        #region Constructors
 
        /// <summary>
        /// Construct a shapeable characters object
        /// </summary>
        /// <remarks>
        /// The shapeTypeface parameter can be null if and only if CheckFastPathNominalGlyphs
        /// has previously returned true.
        /// </remarks>
        internal TextShapeableCharacters(
            CharacterBufferRange    characterRange,
            TextRunProperties       properties,
            double                  emSize,
            ItemProps               textItem,
            ShapeTypeface           shapeTypeface,
            bool                    nullShape,
            TextFormattingMode      textFormattingMode,
            bool isSideways
            )
        {
            _isSideways = isSideways;
            _textFormattingMode = textFormattingMode;
            _characterBufferRange = characterRange;
            _properties = properties;
            _emSize = emSize;
            _textItem = textItem;
            _shapeTypeface = shapeTypeface;
            _nullShape = nullShape;
        }
 
        #endregion
 
        #region TextRun implementation
 
        /// <summary>
        /// Character reference
        /// </summary>
        public sealed override CharacterBufferReference CharacterBufferReference
        {
            get
            {
                return _characterBufferRange.CharacterBufferReference;
            }
        }
 
 
        /// <summary>
        /// Character length of the run
        /// </summary>
        public sealed override int Length
        {
            get
            {
                return _characterBufferRange.Length;
            }
        }
 
 
        /// <summary>
        /// A set of properties shared by every characters in the run
        /// </summary>
        public sealed override TextRunProperties Properties
        {
            get
            {
                return _properties;
            }
        }
 
        #endregion
 
 
        #region TextShapeableSymbols implementation
 
        /// <summary>
        /// Compute a shaped glyph run object from specified glyph-based info
        /// </summary>
        internal sealed override GlyphRun ComputeShapedGlyphRun(
            Point                    origin,
            char[]                   characterString,
            ushort[]                 clusterMap,
            ushort[]                 glyphIndices,
            IList<double>            glyphAdvances,
            IList<Point>             glyphOffsets,
            bool                     rightToLeft,
            bool                     sideways
            )
        {
            Invariant.Assert(_shapeTypeface != null);
            Invariant.Assert(glyphIndices   != null);
            // Device fonts are only used through the LS non-glyphed code path. Only when a DigitCulture is set
            // will a potential device font be ignored and come through shaping.
            Invariant.Assert(_shapeTypeface.DeviceFont == null  || _textItem.DigitCulture != null);
 
            bool[] caretStops = null;
 
            if (    clusterMap != null
                &&  (HasExtendedCharacter || NeedsCaretInfo)
                )
            {
                caretStops = new bool[clusterMap.Length + 1];
 
                // caret stops at cluster boundaries, the first and the last entries are always set
                caretStops[0] = true;
                caretStops[clusterMap.Length] = true;
 
                ushort lastGlyph = clusterMap[0];
 
                for (int i = 1; i < clusterMap.Length; i++)
                {
                    ushort glyph = clusterMap[i];
 
                    if (glyph != lastGlyph)
                    {
                        caretStops[i] = true;
                        lastGlyph = glyph;
                    }
                }
            }
 
            return GlyphRun.TryCreate(
                _shapeTypeface.GlyphTypeface,
                (rightToLeft ? 1 : 0),
                sideways,
                _emSize,
                (float)_properties.PixelsPerDip,
                glyphIndices,
                origin,
                glyphAdvances,
                glyphOffsets,
                characterString,
                null,
                clusterMap,
                caretStops,
                XmlLanguage.GetLanguage(CultureMapper.GetSpecificCulture(_properties.CultureInfo).IetfLanguageTag),
                _textFormattingMode
                );
        }
 
        private GlyphTypeface GetGlyphTypeface(out bool nullFont)
        {
            GlyphTypeface glyphTypeface;
 
            if (_shapeTypeface == null)
            {
                // We're in the optimized path where the GlyphTypeface depends only
                // on the Typeface, not on the particular input characters.
                Typeface typeface = _properties.Typeface;
 
                // Get the GlyphTypeface.
                glyphTypeface = typeface.TryGetGlyphTypeface();
 
                // If Typeface does not specify *any* valid font family, then we use
                // the GlyphTypeface for Arial but only to display missing glyphs.
                nullFont = typeface.NullFont;
            }
            else
            {
                // Font linking has mapped the input to a specific GlyphTypeface.
                glyphTypeface = _shapeTypeface.GlyphTypeface;
 
                // If font linking could not find *any* physical font family, then we
                // use the GlyphTypeface for Arial but only to display missing glyphs.
                nullFont = _nullShape;
            }
 
            Invariant.Assert(glyphTypeface != null);
            return glyphTypeface;
        }
 
        /// <summary>
        /// Compute unshaped glyph run object from the specified character-based info
        /// </summary>
        internal sealed override GlyphRun ComputeUnshapedGlyphRun(
            Point         origin,
            char[]        characterString,
            IList<double> characterAdvances
            )
        {
            bool nullFont;
            GlyphTypeface glyphTypeface = GetGlyphTypeface(out nullFont);
            Invariant.Assert(glyphTypeface != null);
 
            return glyphTypeface.ComputeUnshapedGlyphRun(
                origin,
                new CharacterBufferRange(
                    characterString,
                    0,  // offsetToFirstChar
                    characterString.Length
                    ),
                characterAdvances,
                _emSize,
                (float)_properties.PixelsPerDip,
                _properties.FontHintingEmSize,
                nullFont,
                CultureMapper.GetSpecificCulture(_properties.CultureInfo),
                (_shapeTypeface == null  ||  _shapeTypeface.DeviceFont == null) ? null : _shapeTypeface.DeviceFont.Name,
                _textFormattingMode
            );
        }
 
 
        /// <summary>
        /// Draw glyph run to the drawing surface
        /// </summary>
        internal sealed override void Draw(
            DrawingContext      drawingContext,
            Brush               foregroundBrush,
            GlyphRun            glyphRun
            )
        {
            ArgumentNullException.ThrowIfNull(drawingContext);
 
            glyphRun.EmitBackground(drawingContext, _properties.BackgroundBrush);
 
            drawingContext.DrawGlyphRun(
                foregroundBrush != null ? foregroundBrush : _properties.ForegroundBrush,
                glyphRun
                );
        }
 
        internal override double EmSize
        {
            get
            {
                return _emSize;
            }
        }
 
        internal override MS.Internal.Text.TextInterface.ItemProps ItemProps
        {
            get
            {
                return _textItem;
            }
        }
 
        /// <summary>
        /// Get advance widths of unshaped characters
        /// </summary>
        internal sealed override unsafe void GetAdvanceWidthsUnshaped(
            char*         characterString,
            int           characterLength,
            double        scalingFactor,
            int*          advanceWidthsUnshaped
            )
        {
            if (!IsShapingRequired)
            {
                if (    (_shapeTypeface            != null)
                    &&  (_shapeTypeface.DeviceFont != null))
                {
                    // Use device font to compute advance widths
                    _shapeTypeface.DeviceFont.GetAdvanceWidths(
                        characterString,
                        characterLength,
                        _emSize * scalingFactor,
                        advanceWidthsUnshaped
                    );
                }
                else
                {
                    bool nullFont;
                    GlyphTypeface glyphTypeface = GetGlyphTypeface(out nullFont);
                    Invariant.Assert(glyphTypeface != null);
 
                    glyphTypeface.GetAdvanceWidthsUnshaped(
                        characterString,
                        characterLength,
                        _emSize,
                        (float)_properties.PixelsPerDip,
                        scalingFactor,
                        advanceWidthsUnshaped,
                        nullFont,
                        _textFormattingMode,
                        _isSideways
                        );
                }
            }
            else
            {
                GlyphTypeface glyphTypeface = _shapeTypeface.GlyphTypeface;
 
                Invariant.Assert(glyphTypeface != null);
                Invariant.Assert(characterLength > 0);
 
                CharacterBufferRange newBuffer = new CharacterBufferRange(characterString, characterLength);
                MS.Internal.Text.TextInterface.GlyphMetrics[] glyphMetrics = BufferCache.GetGlyphMetrics(characterLength);
 
                glyphTypeface.GetGlyphMetricsOptimized(newBuffer,
                                                       _emSize,
                                                       (float)_properties.PixelsPerDip,
                                                       _textFormattingMode,
                                                       _isSideways,
                                                       glyphMetrics
                                                       );
 
                if (_textFormattingMode == TextFormattingMode.Display &&
                    TextFormatterContext.IsSpecialCharacter(*characterString))
                {
                    // If the run starts with a special character (in
                    // the LineServices sense), we apply display-mode rounding now,
                    // as we won't get another chance.   This assumes that the characters
                    // in a run are either all special or all non-special;  that assumption
                    // is valid in the current LS implementation.
                    double designToEm = _emSize / glyphTypeface.DesignEmHeight;
                    double pixelsPerDip = _properties.PixelsPerDip;
 
                    for (int i = 0; i < characterLength; i++)
                    {
                        advanceWidthsUnshaped[i] = (int)Math.Round(TextFormatterImp.RoundDipForDisplayMode(glyphMetrics[i].AdvanceWidth * designToEm, pixelsPerDip) * scalingFactor);
                    }
                }
                else
                {
                    // For the normal case, rounding is applied later on when LS
                    // invokes the callback GetGlyphPositions, so that adjustments
                    // due to justification and shaping are taken into account.
                    double designToEm = _emSize * scalingFactor / glyphTypeface.DesignEmHeight;
 
                    for (int i = 0; i < characterLength; i++)
                    {
                        advanceWidthsUnshaped[i] = (int)Math.Round(glyphMetrics[i].AdvanceWidth * designToEm);
                    }
                }
 
 
                BufferCache.ReleaseGlyphMetrics(glyphMetrics);
            }
        }
 
        /// <summary>
        /// This is needed to decide whether we should pass a max cluster size to LS.
        /// We pass a max cluster size to LS to perform line breaking correctly.
        /// Passing a max cluster size larger than necessary will have impact on perf.
        /// </summary>
        internal sealed override bool NeedsMaxClusterSize
        {
            get
            {
                //  We will need to pass a max cluster size to LS if:
                //  1) the script is not Latin (hence more likely to formed into ligature)
                //  2) or the run contains combining mark or extended characters
                if (!_textItem.IsLatin ||
                    _textItem.HasCombiningMark ||
                    _textItem.HasExtendedCharacter
                    )
                {
                    return true;
                }
 
                return false;
            }
        }
 
        /// <summary>
        /// Return value indicates whether two runs can shape together
        /// </summary>
        internal sealed override bool CanShapeTogether(
            TextShapeableSymbols   shapeable
            )
        {
            TextShapeableCharacters charShape = shapeable as TextShapeableCharacters;
 
            if (charShape == null)
                return false;
 
            return
                    _shapeTypeface.Equals(charShape._shapeTypeface)
                // Extended characters need to be shaped by surrogate shaper. They cannot be shaped together with non-exteneded characters.
                && (_textItem.HasExtendedCharacter) == (charShape._textItem.HasExtendedCharacter)
                && _emSize == charShape._emSize
                && (
                    _properties.CultureInfo == null ?
                        charShape._properties.CultureInfo == null
                      : _properties.CultureInfo.Equals(charShape._properties.CultureInfo)
                    )
                && _nullShape == charShape._nullShape
                && (_textItem.CanShapeTogether(charShape._textItem));
        }
 
 
        /// <summary>
        /// Indicate whether run cannot be treated as simple characters because shaping is required.
        ///
        /// The following cases use simple rendering without shaping:
        ///   o  No _shapeTypeface. This happens in very simple rendering cases.
        ///   o  Non-Unicode (i.e. symbol) fonts.
        ///   o  When using a device font.
        ///
        /// Note that the presence of a device font in _shapeTypeface.DeviceFont implies use of
        /// a device font in all cases except where digit substitution applies. This special
        /// case occurs because the cached result per codepoint of TypefaceMap must include the device font
        /// for non-western digits in order to support device font rendering of the non-Western
        /// digit Unicode codepoints. The device font is not used however when the non-Western digits
        /// are displayed as a result of digit substitution from backing store Western digits.
        /// </summary>
        internal sealed override bool IsShapingRequired
        {
            get
            {
                return
                        (_shapeTypeface != null)                 // Can't use shaping without a shape typeface
                    &&  (    (_shapeTypeface.DeviceFont == null) // Can't use shaping when rendering with a device font
                         ||  (_textItem.DigitCulture != null))   //   -- unless substituting digits
                    &&  (!IsSymbol);                             // Can't use shaping for symbol (non-Unicode) fonts
            }
        }
 
 
        /// <summary>
        /// A Boolean value indicates whether additional info is required for caret positioning
        /// </summary>
        internal sealed override bool NeedsCaretInfo
        {
            get
            {
                return (_textItem.HasCombiningMark)
                    || (_textItem.NeedsCaretInfo);
            }
        }
 
 
        /// <summary>
        /// A Boolean value indicates whether run has extended character
        /// </summary>
        internal sealed override bool HasExtendedCharacter
        {
            get
            {
                return _textItem.HasExtendedCharacter;
            }
        }
 
 
        /// <summary>
        /// Run height
        /// </summary>
        internal sealed override double Height
        {
            get
            {
                return _properties.Typeface.LineSpacing(_properties.FontRenderingEmSize, 1, _properties.PixelsPerDip, _textFormattingMode);
            }
        }
 
 
        /// <summary>
        /// Distance from top to baseline
        /// </summary>
        internal sealed override double Baseline
        {
            get
            {
                return _properties.Typeface.Baseline(_properties.FontRenderingEmSize, 1, _properties.PixelsPerDip, _textFormattingMode);
            }
        }
 
 
        /// <summary>
        /// Distance from baseline to underline position relative to TextRunProperties.FontRenderingEmSize
        /// </summary>
        internal sealed override double UnderlinePosition
        {
            get
            {
                return _properties.Typeface.UnderlinePosition;
            }
        }
 
 
        /// <summary>
        /// Underline thickness relative to TextRunProperties.FontRenderingEmSize
        /// </summary>
        internal sealed override double UnderlineThickness
        {
            get
            {
                return _properties.Typeface.UnderlineThickness;
            }
        }
 
 
        /// <summary>
        /// Distance from baseline to strike-through position relative to TextRunProperties.FontRenderingEmSize
        /// </summary>
        internal sealed override double StrikethroughPosition
        {
            get
            {
                return _properties.Typeface.StrikethroughPosition;
            }
        }
 
 
        /// <summary>
        /// strike-through thickness relative to TextRunProperties.FontRenderingEmSize
        /// </summary>
        internal sealed override double StrikethroughThickness
        {
            get
            {
                return _properties.Typeface.StrikethroughThickness;
            }
        }
 
        #endregion
 
 
        /// <summary>
        /// Whether all characters in this run are non-Unicode character (symbol)
        /// </summary>
        internal bool IsSymbol
        {
            get
            {
                if (_shapeTypeface != null)
                    return _shapeTypeface.GlyphTypeface.Symbol;
 
                return _properties.Typeface.Symbol;
            }
        }
 
        internal override GlyphTypeface GlyphTypeFace
        {
            get
            {
                if (_shapeTypeface != null)
                    return _shapeTypeface.GlyphTypeface;
 
                return _properties.Typeface.TryGetGlyphTypeface();
            }
        }
 
        /// <summary>
        /// Returns maximum possible cluster size for the run.  Normally, this
        /// is 8 characters, but Indic scripts require this to be 15.
        /// </summary>
        internal const ushort DefaultMaxClusterSize = 8;
        private  const ushort IndicMaxClusterSize = 15;
        internal sealed override ushort MaxClusterSize
        {
            get
            {
                if (_textItem.IsIndic)
                {
                    return IndicMaxClusterSize;
                }
 
                return DefaultMaxClusterSize;
            }
}
    }
}