File: MS\Internal\Shaping\Substitution.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:  OpentTypeLayout substitution classes
 
using System.IO;
 
namespace MS.Internal.Shaping
{
    /// <remarks>
    /// Correct algorithm for multiple substitution hasn't been implemented yet.
    /// </remarks>
    internal struct SingleSubstitutionSubtable
    {
        private const int offsetFormat = 0;
        private const int offsetCoverage = 2;
        private const int offsetFormat1DeltaGlyphId = 4;
        private const int offsetFormat2GlyphCount = 4;
        private const int offsetFormat2SubstitutehArray = 6;
        private const int sizeFormat2SubstituteSize = 2;
 
        private ushort Format(FontTable Table)
        {
            return Table.GetUShort(offset + offsetFormat);
        }
        
        private CoverageTable Coverage(FontTable Table)
        {
            return new CoverageTable(offset+Table.GetUShort(offset + offsetCoverage));
        }
        
        private short Format1DeltaGlyphId(FontTable Table)
        {
            Invariant.Assert(Format(Table)==1);
            return Table.GetShort(offset + offsetFormat1DeltaGlyphId);
        }
        
        // Not used. This value should be equal to glyph count in Coverage.
        // Keeping it for future reference
        //private ushort Foramt2GlyphCount(FontTable Table)
        //{
        //    Debug.Assert(Format(Table)==2);
        //    return Table.GetUShort(offset + offsetFormat2GlyphCount);
        //}       
        private ushort Format2SubstituteGlyphId(FontTable Table,ushort Index)
        {
            Invariant.Assert(Format(Table)==2);
            return Table.GetUShort(offset + offsetFormat2SubstitutehArray + 
                                            Index * sizeFormat2SubstituteSize);
        }
 
        public bool Apply(
                            FontTable          Table,
                            GlyphInfoList   GlyphInfo,      // List of GlyphInfo structs
                            int             FirstGlyph,     // where to apply it
                            out int         NextGlyph       // Next glyph to process
                         )
        {
            Invariant.Assert(FirstGlyph >= 0);
        
            NextGlyph = FirstGlyph + 1; //In case we don't match;
                
            ushort GlyphId = GlyphInfo.Glyphs[FirstGlyph];
            int CoverageIndex = Coverage(Table).GetGlyphIndex(Table,GlyphId);
            if (CoverageIndex == -1) return false;
            
            switch(Format(Table))
            {
                case 1:
                    GlyphInfo.Glyphs[FirstGlyph] = (ushort)(GlyphId + Format1DeltaGlyphId(Table));
                    GlyphInfo.GlyphFlags[FirstGlyph] = (ushort)(GlyphFlags.Unresolved | GlyphFlags.Substituted);
                    NextGlyph = FirstGlyph + 1;
                    return true;
 
                case 2:
                    GlyphInfo.Glyphs[FirstGlyph] = Format2SubstituteGlyphId(Table,(ushort)CoverageIndex);
                    GlyphInfo.GlyphFlags[FirstGlyph] = (ushort)(GlyphFlags.Unresolved | GlyphFlags.Substituted);
                    NextGlyph = FirstGlyph + 1;
                    return true;
 
                default:
                    NextGlyph = FirstGlyph+1;
                    return false;
            }
        }
 
        public bool IsLookupCovered(
                        FontTable table, 
                        uint[] glyphBits, 
                        ushort minGlyphId, 
                        ushort maxGlyphId)
        {
            return Coverage(table).IsAnyGlyphCovered(table,
                                                     glyphBits,
                                                     minGlyphId,
                                                     maxGlyphId
                                                    );        
        }
        
        public CoverageTable GetPrimaryCoverage(FontTable table)
        {
            return Coverage(table);
        }
        
        public SingleSubstitutionSubtable(int Offset) { offset = Offset; }
        private int offset;
    }
 
 
    internal struct LigatureSubstitutionSubtable
    {
        private const int offsetFormat = 0;
        private const int offsetCoverage = 2;
        private const int offsetLigatureSetCount = 4;
        private const int offsetLigatureSetArray = 6;
        private const int sizeLigatureSet = 2;
         
        private ushort Format(FontTable Table)
        {
            return Table.GetUShort(offset + offsetFormat);
        }
        
        private CoverageTable Coverage(FontTable Table)
        {
            return new CoverageTable(offset + Table.GetUShort(offset + offsetCoverage));
        }
 
        private ushort LigatureSetCount(FontTable Table)
        {
            return Table.GetUShort(offset + offsetLigatureSetCount);
        }
        
        private LigatureSetTable LigatureSet(FontTable Table, ushort Index)
        {
            return new LigatureSetTable(offset+Table.GetUShort(offset+
                                                               offsetLigatureSetArray + 
                                                               Index * sizeLigatureSet));
        }
 
#region Ligature Substitution subtable private structures        
        private struct LigatureSetTable
        {
            private const int offsetLigatureCount = 0;
            private const int offsetLigatureArray = 2;
            private const int sizeLigatureOffset  = 2;
 
            public ushort LigatureCount(FontTable Table)
            {
                return Table.GetUShort(offset + offsetLigatureCount);
            }
        
            public LigatureTable Ligature(FontTable Table, ushort Index)
            {
                return new LigatureTable(offset + Table.GetUShort(offset + 
                    offsetLigatureArray + 
                    Index * sizeLigatureOffset));
            }
        
            public LigatureSetTable(int Offset) { offset = Offset; }
            private int offset;
        }
 
        private struct LigatureTable
        {
            private const int offsetLigatureGlyph = 0;
            private const int offsetComponentCount = 2;
            private const int offsetComponentArray = 4;
            private const int sizeComponent = 2;
    
            public ushort LigatureGlyph(FontTable Table)
            {
                return Table.GetUShort(offset + offsetLigatureGlyph);
            }
        
            public ushort ComponentCount(FontTable Table)
            {
                return Table.GetUShort(offset + offsetComponentCount);
            }
 
            public ushort Component(FontTable Table, ushort Index)
            {
                //LigaTable includes comps from 1 to N. So, (Index-1)
                return Table.GetUShort(offset + offsetComponentArray +
                    (Index-1) * sizeComponent);
            }
        
            public LigatureTable(int Offset) { offset = Offset; }
            private int offset;
        }
#endregion
 
        public unsafe bool Apply(
                            IOpenTypeFont   Font,
                            FontTable       Table,
                            int             CharCount,
                            UshortList      Charmap,        // Character to glyph map
                            GlyphInfoList   GlyphInfo,      // List of GlyphInfo
                            ushort          LookupFlags,    // Lookup flags for glyph lookups
                            int             FirstGlyph,     // where to apply it
                            int             AfterLastGlyph, // how long is a context we can use
                            out int         NextGlyph       // Next glyph to process
                          )
        {
            Invariant.Assert(FirstGlyph>=0);
            Invariant.Assert(AfterLastGlyph<=GlyphInfo.Length);
 
            NextGlyph = FirstGlyph + 1; //In case we don't match;
            
            if (Format(Table) != 1) return false; // Unknown format
                
            int glyphCount=GlyphInfo.Length;
            ushort glyphId = GlyphInfo.Glyphs[FirstGlyph];
            int CoverageIndex = Coverage(Table).GetGlyphIndex(Table,glyphId);
            if (CoverageIndex==-1) return false;
 
            int curGlyph;
            ushort ligatureGlyph=0;
            bool match = false;
            ushort compCount=0;
 
            LigatureSetTable ligatureSet = LigatureSet(Table,(ushort)CoverageIndex);
            ushort ligaCount = ligatureSet.LigatureCount(Table);
            for(ushort liga=0; liga<ligaCount; liga++)
            {
                LigatureTable ligature = ligatureSet.Ligature(Table,liga);
                compCount = ligature.ComponentCount(Table);
                if (compCount == 0)
                {
                    throw new FileFormatException();
                }
                
                curGlyph=FirstGlyph;
                ushort comp=1;
                for(comp=1;comp<compCount;comp++)
                {
                    curGlyph = LayoutEngine.GetNextGlyphInLookup(Font,GlyphInfo,curGlyph+1,LookupFlags,LayoutEngine.LookForward);
                    if (curGlyph>=AfterLastGlyph) break;
                    
                    if (GlyphInfo.Glyphs[curGlyph]!=ligature.Component(Table,comp)) break;
                }
                
                if (comp==compCount) //liga matched
                {
                    match=true;
                    ligatureGlyph = ligature.LigatureGlyph(Table);
                    break; //Liga found
                }
            }
            //If no ligature found, match will remain false after last iteration
            
            if (match) 
            {
                //Fix character and glyph Mapping
            
                //PERF: localize ligature character range
                    
                //Calculate Ligature CharCount
                int totalLigaCharCount=0;
                int firstLigaChar=int.MaxValue;
                curGlyph=FirstGlyph;
                for(ushort comp=0;comp<compCount;comp++)
                {
                    Invariant.Assert(curGlyph<AfterLastGlyph);
                    
                    int curFirstChar = GlyphInfo.FirstChars[curGlyph];
                    int curLigaCount = GlyphInfo.LigatureCounts[curGlyph];
                    
                    totalLigaCharCount += curLigaCount;
                    if (curFirstChar<firstLigaChar) firstLigaChar=curFirstChar;
                    
                    curGlyph = LayoutEngine.GetNextGlyphInLookup(Font,GlyphInfo,curGlyph+1,LookupFlags,LayoutEngine.LookForward);
                }
                    
                curGlyph=FirstGlyph;
                int prevGlyph=FirstGlyph;
                ushort shift=0;
                for(ushort comp=1;comp<=compCount;comp++)
                {
                    prevGlyph=curGlyph;
                    
                    if (comp<compCount)
                    {
                        curGlyph = LayoutEngine.
                                        GetNextGlyphInLookup(Font,GlyphInfo,
                                                             curGlyph+1,
                                                             LookupFlags,
                                                             LayoutEngine.LookForward);
                    }
                    else curGlyph = GlyphInfo.Length; // to the end from last component
                    
                    // Set charmap for ligature component
                    for(int curChar=0; curChar<CharCount; curChar++)
                    {
                        if (Charmap[curChar]==prevGlyph)
                        {
                            Charmap[curChar] = (ushort)FirstGlyph;
                        }
                    }
 
                    //Shift glyphInfo
                    if (shift>0)
                    {
                        for(int glyph=prevGlyph+1; glyph<curGlyph; glyph++)
                        {
                            GlyphInfo.Glyphs[glyph-shift]         = GlyphInfo.Glyphs[glyph];
                            GlyphInfo.GlyphFlags[glyph-shift]     = GlyphInfo.GlyphFlags[glyph];
                            GlyphInfo.FirstChars[glyph-shift]     = GlyphInfo.FirstChars[glyph];
                            GlyphInfo.LigatureCounts[glyph-shift] = GlyphInfo.LigatureCounts[glyph];
                        }
                    
                        if (curGlyph-prevGlyph>1) //do fixing only if have glyphs in between
                        {
                            for(int curChar=0; curChar<CharCount; curChar++)
                            {
                                ushort curCharmap = Charmap[curChar];
                                if (curCharmap>prevGlyph && curCharmap<curGlyph)
                                {
                                    Charmap[curChar] -= shift;
                                }
                            }
                        }
                    }
                                        
                    ++shift;
                }
                
                //Place new glyph into position of first ligature glyph
                GlyphInfo.Glyphs[FirstGlyph]         = ligatureGlyph;
                GlyphInfo.GlyphFlags[FirstGlyph]     = (ushort)(GlyphFlags.Unresolved | GlyphFlags.Substituted);
                GlyphInfo.FirstChars[FirstGlyph]     = (ushort)firstLigaChar;
                GlyphInfo.LigatureCounts[FirstGlyph] = (ushort)totalLigaCharCount;
                
                //remove empty space
                if (compCount > 1)
                {
                    GlyphInfo.Remove(GlyphInfo.Length-compCount+1,compCount-1);
                }
                
                NextGlyph=prevGlyph-(compCount-1)+1;
            }
            
            return match;
        }
 
        public bool IsLookupCovered(
                        FontTable table, 
                        uint[] glyphBits, 
                        ushort minGlyphId, 
                        ushort maxGlyphId)
        {
            if (!Coverage(table).IsAnyGlyphCovered(table,
                                                     glyphBits,
                                                     minGlyphId,
                                                     maxGlyphId
                                                    )
               ) return false;
 
            ushort ligatureSetCount = LigatureSetCount(table);
                        
            for(ushort setIndex = 0; setIndex < ligatureSetCount; setIndex++)
            {
                LigatureSetTable ligatureSet = LigatureSet(table, setIndex);
                ushort ligaCount = ligatureSet.LigatureCount(table);
                
                for (ushort liga = 0; liga < ligaCount; liga++)
                {
                    LigatureTable ligature = ligatureSet.Ligature(table, liga);
                    ushort compCount = ligature.ComponentCount(table);
                    
                    bool ligatureIsComplex = true;
                    
                    for(ushort compIndex = 1; compIndex < compCount; compIndex++)
                    {
                        ushort glyphId = ligature.Component(table,compIndex);
 
                        if (glyphId > maxGlyphId || 
                            glyphId < minGlyphId ||
                            (glyphBits[glyphId >> 5] & (1 << (glyphId % 32))) == 0
                           )
                        {
                            ligatureIsComplex = false;
                            break;
                        }
                    }
                    
                    if (ligatureIsComplex) return true;
                }
            }
            
            return false;
}
 
        public CoverageTable GetPrimaryCoverage(FontTable table)
        {
            return Coverage(table);
        }
 
        public LigatureSubstitutionSubtable(int Offset) { offset = Offset; }
        private int offset;
    }
 
    internal struct MultipleSubstitutionSequenceTable
    {
        private const int offsetGlyphCount = 0;
        private const int offsetGlyphArray = 2;
        private const int sizeGlyphId      = 2;
 
        public ushort GlyphCount(FontTable Table)
        {
            return Table.GetUShort(offset + offsetGlyphCount);
        }
        
        public ushort Glyph(FontTable Table, ushort index)
        {
            return Table.GetUShort(offset + offsetGlyphArray + index * sizeGlyphId);
        }
 
        public MultipleSubstitutionSequenceTable(int Offset) { offset = Offset; }
        private int offset;
    }
    
    internal struct MultipleSubstitutionSubtable
    {
        private const int offsetFormat = 0;
        private const int offsetCoverage = 2;
        private const int offsetSequenceCount = 4;
        private const int offsetSequenceArray = 6;
        private const int sizeSequenceOffset = 2;
         
        private ushort Format(FontTable Table)
        {
            return Table.GetUShort(offset + offsetFormat);
        }
        
        private CoverageTable Coverage(FontTable Table)
        {
            return new CoverageTable(offset + Table.GetUShort(offset + offsetCoverage));
        }
        
        // Not used. This value should be equal to glyph count in Coverage.
        // Keeping it for future reference
        //private ushort SequenceCount(FontTable Table)
        //{
        //    return Table.GetUShort(offset + offsetSequenceCount);
        //}
        
        private MultipleSubstitutionSequenceTable Sequence(FontTable Table, int Index)
        {
            return new MultipleSubstitutionSequenceTable(
                                        offset + 
                                        Table.GetUShort(offset +
                                                        offsetSequenceArray +
                                                        Index * sizeSequenceOffset)
                                       );
        }
 
        public unsafe bool Apply(
            IOpenTypeFont   Font,
            FontTable       Table,
            int             CharCount,
            UshortList      Charmap,        // Character to glyph map
            GlyphInfoList   GlyphInfo,      // List of GlyphInfo
            ushort          LookupFlags,    // Lookup flags for glyph lookups
            int             FirstGlyph,     // where to apply it
            int             AfterLastGlyph, // how long is a context we can use
            out int         NextGlyph       // Next glyph to process
            )
         {
            NextGlyph = FirstGlyph + 1; // in case we don't match
            
            if (Format(Table) != 1) return false; //unknown format
            
            int oldGlyphCount=GlyphInfo.Length;
            
            ushort glyphId = GlyphInfo.Glyphs[FirstGlyph];
            int coverageIndex = Coverage(Table).GetGlyphIndex(Table,glyphId);
            if (coverageIndex==-1) return false;
            
            MultipleSubstitutionSequenceTable sequence = Sequence(Table,coverageIndex);
 
            ushort sequenceLength = sequence.GlyphCount(Table);
            int lengthDelta = sequenceLength - 1;
            
            if (sequenceLength==0)
            {
                // This is illegal, because mapping will be broken -
                // corresponding char will be lost. Just leave it as it is.
                // (char will be attached to the following glyph).
                GlyphInfo.Remove(FirstGlyph,1);
            }
            else
            {
                ushort firstChar = GlyphInfo.FirstChars[FirstGlyph];
                ushort ligatureCount = GlyphInfo.LigatureCounts[FirstGlyph];
 
                if (lengthDelta > 0)
                {
                    GlyphInfo.Insert(FirstGlyph,lengthDelta);
                }
                
                //put glyphs in place
                for(ushort gl=0; gl<sequenceLength; gl++)
                {
                    GlyphInfo.Glyphs[FirstGlyph + gl] = sequence.Glyph(Table,gl);
                    GlyphInfo.GlyphFlags[FirstGlyph + gl] =
                                (ushort)(GlyphFlags.Unresolved | GlyphFlags.Substituted);
                    GlyphInfo.FirstChars[FirstGlyph + gl] = firstChar;
                    GlyphInfo.LigatureCounts[FirstGlyph + gl] = ligatureCount;
                }
            }
            
            // Fix char mapping - very simple for now. 
            // Works only for arabic base+mark -> base and marks decomposition
            // Needs work for full mapping
            for(int ch=0;ch<CharCount;ch++)
            {
                if (Charmap[ch]>FirstGlyph) Charmap[ch] = (ushort)(Charmap[ch]+lengthDelta);
            }
            
            NextGlyph = FirstGlyph + lengthDelta + 1;
            
            return true;
         }
 
        public bool IsLookupCovered(
                        FontTable table, 
                        uint[] glyphBits, 
                        ushort minGlyphId, 
                        ushort maxGlyphId)
        {
            return Coverage(table).IsAnyGlyphCovered(table,
                                                     glyphBits,
                                                     minGlyphId,
                                                     maxGlyphId
                                                    );
        }
 
        public CoverageTable GetPrimaryCoverage(FontTable table)
        {
            return Coverage(table);
        }
 
        public MultipleSubstitutionSubtable(int Offset) { offset = Offset; }
        private int offset;
    }
 
    struct AlternateSubstitutionSubtable
    {
        private const int offsetFormat              = 0;
        private const int offsetCoverage            = 2;
        private const int offsetAlternateSetCount   = 4;
        private const int offsetAlternateSets       = 6;
        private const int sizeAlternateSetOffset    = 2;
 
        private const ushort InvalidAlternateGlyph = 0xFFFF;
 
        public ushort Format(FontTable Table)
        {
            return Table.GetUShort(offset + offsetFormat);
        }
        
        private CoverageTable Coverage(FontTable Table)
        {
            return new CoverageTable(offset + Table.GetUShort(offset + offsetCoverage));
        }
        
        // Not used. This value should be equal to glyph count in Coverage.
        // Keeping it for future reference
        //private ushort AlternateSetCount(FontTable Table)
        //{
        //    return Table.GetUShort(offset + offsetAlternateSetCount);
        //}
        
        private AlternateSetTable AlternateSet(FontTable Table, int index)
        {
            return new AlternateSetTable(offset + 
                                         Table.GetUShort(offset + 
                                                         offsetAlternateSets +
                                                         index * sizeAlternateSetOffset)
                                        );
        }
 
        private struct AlternateSetTable
        {
            private const int offsetGlyphCount = 0;
            private const int offsetGlyphs     = 2;
            private const int sizeGlyph        = 2;
 
            public ushort GlyphCount(FontTable Table)
            {
                return Table.GetUShort(offset + offsetGlyphCount);
            }
            
            public ushort Alternate(FontTable Table, uint FeatureParam)
            {
                Invariant.Assert(FeatureParam > 0); // Parameter 0 means feautre is disabled.
                                                //Should be filtered out in GetNextEnabledGlyphRange
 
                // Off by one - alternate number 1 is stored under index 0
                uint index = FeatureParam - 1;
                
                if (index >= GlyphCount(Table)) 
                {
                    return AlternateSubstitutionSubtable.InvalidAlternateGlyph;
                }
                
                return Table.GetUShort(offset + offsetGlyphs + (ushort)index*sizeGlyph);
            }
            
            public AlternateSetTable(int Offset) { offset = Offset; }
            private int offset;
        }
 
        public unsafe bool Apply(
            FontTable       Table,
            GlyphInfoList   GlyphInfo,      // List of GlyphInfo
            uint            FeatureParam,   // For this lookup - index of glyph alternate
            int             FirstGlyph,     // where to apply it
            out int         NextGlyph       // Next glyph to process
            )
        {
            NextGlyph = FirstGlyph + 1; // always move one glyph forward, 
                                        // doesn't matter whether we matched context
                                        
            if (Format(Table) != 1) return false; //Unknown format
            
            int oldGlyphCount=GlyphInfo.Length;
            
            int coverageIndex = Coverage(Table).
                                    GetGlyphIndex(Table,GlyphInfo.Glyphs[FirstGlyph]);
            if (coverageIndex==-1) return false;
            
            AlternateSetTable alternateSet = AlternateSet(Table,coverageIndex);
            
            ushort alternateGlyph = alternateSet.Alternate(Table, FeatureParam);
 
            if (alternateGlyph != InvalidAlternateGlyph)
            {
                GlyphInfo.Glyphs[FirstGlyph] = alternateGlyph;
                GlyphInfo.GlyphFlags[FirstGlyph] = (ushort)(GlyphFlags.Unresolved | GlyphFlags.Substituted);
                return true;
            }
 
            return false;            
        }
        
        public bool IsLookupCovered(
                        FontTable table, 
                        uint[] glyphBits, 
                        ushort minGlyphId, 
                        ushort maxGlyphId)
        {
            return Coverage(table).IsAnyGlyphCovered(table,
                                                     glyphBits,
                                                     minGlyphId,
                                                     maxGlyphId
                                                    );
        }
 
        public CoverageTable GetPrimaryCoverage(FontTable table)
        {
            return Coverage(table);
        }
 
        public AlternateSubstitutionSubtable(int Offset) { offset = Offset; }
        private int offset;
    }
}