OpenTypeLayoutCache.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Core / CSharp / MS / Internal / Shaping / OpenTypeLayoutCache.cs / 1305600 / OpenTypeLayoutCache.cs

                            //---------------------------------------------------------------------------- 
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Description: The OpenTypeLayoutCache class is used by OpenType layout services 
//              for performance optimizations. It is responsible for creating cache
//              structures as well as runtime support during time of actual layout calls. 
// 
// History:
//  03/28/2005 : sergeym - Created 
//
//---------------------------------------------------------------------------

using System; 
using System.Security;
using System.Security.Permissions; 
using System.IO; 
using System.Diagnostics;
using System.Collections; 

using MS.Internal;
using MS.Internal.FontCache;
 
namespace MS.Internal.Shaping
{ 
    // Cache is stroing for each glyph list of lookups where this glyph participate as 
    // primary (first in rule sequence), and so can be substituted or positioned.
    // At runtime, main loop will attempt to apply lookups only to places where there 
    // is a potential for substitution.
    //
    // Cache is of following structure. It contains a list of all glyphs that are affected
    // and each glyph has a list of lookups associated with it. Each list is terminated by 0xffff 
    // value for glyph index.
    // 
    // Glyph1 -> Lookup1.1, Lookup1.2, ..., 0xffff 
    // ...
    // GlyphN -> LookupN.1, LookupN.2, ... 0xfffff 
    //
    // Binary structure consists of a header, list of glyphs and offsets and area where actual
    // lookup lists reside. Offsets to lookup lists are relative to the start of the table:
    // 
    // USHORT totalCacheSize           // total size of table cache
    // USHORT 0xFFFF                   // glyphs not present assumed pointing to this empty list 
    // USHORT lookupCount              // number of lookups fit into cache 
    // USHORT glyphCount               // number of glyph records in the cache
    // (                               // array of glyph records, each has 
    //     USHORT glyphId,             //     glyph id and
    //     USHORT lookupListOffset     //     offset to lookup list, from the cache start
    // ) [glyphCount]                  //
    // USHORT[] lookupLists            // Here is the area where lookup lists reside. Each list 
    //                                 // is in ascending order, terminataed by 0xffff. Several glyphs
    //                                 // may point to the same list, for saving space. 
    // 
    // Cache buildig code in CreateTableCache does simple size optimiation, comparing two
    // consecutive glyphs if they have the same list of glyphs and point both to the same 
    // physical list. If all lookups can not fit into cache, cache will remember how many
    // actually fit and simply assume that the rest of lookups applicable to all glyphs.
    //
    // During runtime, OTLS supports array of pointers to cache lookup lists, parallel to 
    // array of glyphs. When processing lookup 'i', all pointers a being moved furhter through
    // the list to point to lookup that is >= i. Simple loop through pointers (FindNextLookup) 
    // allows to find next lookup index to be processed. Then in FindNextGlyphInLookup, going 
    // through pointers allows to find next glyph that should be tried for this lookup.
    // 
    // Since every list is terminated by 0xffff, loop in FindNextLookup:
    //          while(*cachePointers[i] < firstLookupIndex) cachePointers[i]++;
    // will never overrun the cache, and so does not require special check or additional data
    // to indicate end of the list. 
    //
    ///  
    /// Implements OpenType layout services cache logic at both caching and using time 
    /// 
    internal static class OpenTypeLayoutCache 
    {
        /// 
        ///     Critical: Calls critical code and has unsafe code blocks
        ///     TreatAsSafe: Pointers accessed are checked by probe. 
        /// 
        [SecurityCritical, SecurityTreatAsSafe] 
        public static void InitCache( 
                                IOpenTypeFont   font,
                                OpenTypeTags    tableTag, 
                                GlyphInfoList   glyphInfo,
                                OpenTypeLayoutWorkspace workspace
                            )
        { 
            Debug.Assert(tableTag == OpenTypeTags.GSUB || tableTag == OpenTypeTags.GPOS);
 
            byte[] cacheArray = font.GetTableCache(tableTag); 

            unsafe 
            {
                if (cacheArray == null)
                {
                    workspace.TableCacheData = null; 
                }
                else 
                { 
                    workspace.TableCacheData = cacheArray;
 
                    workspace.AllocateCachePointers(glyphInfo.Length);
                    RenewPointers(glyphInfo, workspace, 0, glyphInfo.Length);
                }
            } 
        }
 
        ///  
        ///     Critical: Calls critical code
        ///  
        [SecurityCritical]
        public static void OnGlyphsChanged(
                                            OpenTypeLayoutWorkspace workspace,
                                            GlyphInfoList           glyphInfo, 
                                            int                     oldLength,
                                            int                     firstGlyphChanged, 
                                            int                     afterLastGlyphChanged 
                                          )
        { 
            unsafe
            {
                if (workspace.TableCacheData == null)
                { 
                    return;
                } 
            } 

            workspace.UpdateCachePointers(oldLength, glyphInfo.Length, firstGlyphChanged, afterLastGlyphChanged); 
            RenewPointers(glyphInfo, workspace, firstGlyphChanged, afterLastGlyphChanged);
        }

        ///  
        /// Gets number of lookups that fit into table cache
        ///  
        /// In: Storage for buffers we need 
        /// Number of lookups in cache
        ///  
        ///     Critical:  Accesses font cache pointers
        /// 
        [SecurityCritical]
        private static unsafe ushort GetCacheLookupCount(OpenTypeLayoutWorkspace workspace) 
        {
            // If there is no chache, just exit 
            if (workspace.TableCacheData == null) 
            {
                return 0; 
            }
            fixed (byte* pCacheByte = &workspace.TableCacheData[0])
            {
                ushort* pCache = (ushort*)pCacheByte; 

                return pCache[2]; 
            } 
        }
 
        /// 
        /// Find next glyph in lookup. Depending on search direction,
        /// it will update either firstGlyph or afterLastGlyph
        ///  
        /// In: Storage for buffers we need
        /// In: Glyph run 
        /// In: Minimal lookup index to search for. 
        /// Out: Lookup index found
        /// Out: First applicable glyph for this lookup 
        /// True if any lookup found, false otherwise
        /// 
        ///     Critical:  Accesses font cache pointers
        ///  
        [SecurityCritical]
        public static unsafe void FindNextLookup( 
                                    OpenTypeLayoutWorkspace workspace, 
                                    GlyphInfoList glyphInfo,
                                    ushort     firstLookupIndex, 
                                    out ushort lookupIndex,
                                    out int    firstGlyph
                                  )
        { 
            if (firstLookupIndex >= GetCacheLookupCount(workspace))
            { 
                // For lookups that did not fit into cache, just say we should always try it 
                lookupIndex = firstLookupIndex;
                firstGlyph  = 0; 
                return;
            }

            ushort[] cachePointers = workspace.CachePointers; 
            int glyphCount = glyphInfo.Length;
 
            lookupIndex = 0xffff; 
            firstGlyph   = 0;
 
            for(int i = 0; i < glyphCount; i++)
            {
                // [....] up inside the list up to the minimal lookup requested
                // No additional boundary checks are necessary, because every list terminates with 0xffff 
                while(cachePointers[i] < firstLookupIndex) cachePointers[i]++;
                //Now we know that our index is higher or equal than firstLookup index 
 
                if (cachePointers[i] < lookupIndex)
                { 
                    // We now have new minimum
                    lookupIndex = cachePointers[i];
                    firstGlyph  = i;
                } 
            }
 
            if (lookupIndex == 0xffff) 
            {
                // We can't just say we are done, there may be lookups that did not fit into cache 
                lookupIndex = GetCacheLookupCount(workspace);
                firstGlyph  = 0;
            }
        } 

        ///  
        /// Find next glyph in lookup. Depending on search direction, 
        /// it will update either firstGlyph or afterLastGlyph
        ///  
        /// Storage for buffers we need
        /// Current lookup in processing
        /// Do we go forward or backwards
        /// first glyph of search range 
        /// position after last glyph
        /// True if any glyph found, false otherwise 
        ///  
        ///     Critical: unsafe pointer operations
        ///  
        [SecurityCritical]
        public static unsafe bool FindNextGlyphInLookup(
                                    OpenTypeLayoutWorkspace workspace,
                                    ushort          lookupIndex, 
                                    bool            isLookupReversal,
                                    ref int         firstGlyph, 
                                    ref int         afterLastGlyph 
                                )
        { 
            if (lookupIndex >= GetCacheLookupCount(workspace))
            {
                return true;
            } 

            ushort[] cachePointers = workspace.CachePointers; 
 
            if (!isLookupReversal)
            { 
                for (int i = firstGlyph; i < afterLastGlyph; i++)
                {
                    if (cachePointers[i] == lookupIndex)
                    { 
                        firstGlyph = i;
                        return true; 
                    } 
                }
 
                return false;
            }
            else
            { 
                for(int i = afterLastGlyph - 1; i >= firstGlyph; i--)
                { 
                    if (cachePointers[i] == lookupIndex) 
                    {
                        afterLastGlyph = i + 1; 
                        return true;
                    }
                }
 
                return false;
            } 
        } 

        ///  
        ///     Critical: unsafe pointer operations
        /// 
        [SecurityCritical]
        private static unsafe void RenewPointers( 
                                            GlyphInfoList glyphInfo,
                                            OpenTypeLayoutWorkspace workspace, 
                                            int firstGlyph, 
                                            int afterLastGlyph
                                         ) 
        {
            fixed (byte* pCache = &workspace.TableCacheData[0])
            {
                // If there is no chache, just exit 
                if (pCache == null)
                { 
                    return; 
                }
 
                ushort[] cachePointers = workspace.CachePointers;

                for (int i = firstGlyph; i < afterLastGlyph; i++)
                { 
                    ushort glyph = glyphInfo.Glyphs[i];
 
                    // If glyph is not there, we will point to the constant 0xFFFF in the cache 
                    int listOffset = 2;
 
                    //Find glyph entry in the cache
                    int glyphCount = *((ushort*)pCache + 3);
                    ushort* pGlyphs = (ushort*)pCache + 4;
                    int low = 0, high = glyphCount; 

                    while (low < high) 
                    { 
                        int mid = (low + high) >> 1;
                        ushort midGlyph = pGlyphs[mid * 2]; 

                        if (glyph < midGlyph)
                        {
                            high = mid; 
                            continue;
                        } 
                        if (glyph > midGlyph) 
                        {
                            low = mid + 1; 
                            continue;
                        }

                        // Found it! 
                        listOffset = pGlyphs[mid * 2 + 1];
                        break; 
                    } 

                    // Whether we found glyph in the cache or not, 
                    // Pointer will be set to the list, but it may be empty.
                    cachePointers[i] = *((ushort*)(pCache + listOffset));
                }
            } 
        }
 
#region Cache filling 

        ///  
        ///     Critical: Calls critical code
        /// 
        [SecurityCritical]
        internal static void CreateCache(IOpenTypeFont font, int maxCacheSize) 
        {
            if (maxCacheSize > ushort.MaxValue) 
            { 
                // Data structures do not support cache sizes more than 64K.
                maxCacheSize = ushort.MaxValue; 
            }

            int tableCacheSize;
            int totalSize = 0; 

            CreateTableCache(font, OpenTypeTags.GSUB, maxCacheSize - totalSize, out tableCacheSize); 
            totalSize += tableCacheSize; 
            Debug.Assert(totalSize <= maxCacheSize);
 
            CreateTableCache(font, OpenTypeTags.GPOS, maxCacheSize - totalSize, out tableCacheSize);
            totalSize += tableCacheSize;
            Debug.Assert(totalSize <= maxCacheSize);
        } 

        ///  
        ///     Critical: calling FillTableCache to change cache content in unmanaged memory 
        /// 
        [SecurityCritical] 
        private static void CreateTableCache(IOpenTypeFont font, OpenTypeTags tableTag, int maxCacheSize, out int tableCacheSize)
        {
            // Initialize all computed values
            tableCacheSize = 0; 
            int cacheSize = 0;
            int recordCount = 0; 
            int glyphCount = 0; 
            int lastLookupAdded = -1;
            GlyphLookupRecord[] records = null; 

            try
            {
                ComputeTableCache( 
                    font,
                    tableTag, 
                    maxCacheSize, 
                    ref cacheSize,
                    ref records, 
                    ref recordCount,
                    ref glyphCount,
                    ref lastLookupAdded
                    ); 
            }
            catch (FileFormatException) 
            { 
                cacheSize = 0;
            } 

            if (cacheSize > 0)
            {
                tableCacheSize = FillTableCache( 
                    font,
                    tableTag, 
                    cacheSize, 
                    records,
                    recordCount, 
                    glyphCount,
                    lastLookupAdded
                    );
            } 
        }
 
 
        /// 
        ///     Critical: Accessing raw font table 
        /// 
        [SecurityCritical]
        private static void ComputeTableCache(
            IOpenTypeFont           font, 
            OpenTypeTags            tableTag,
            int                     maxCacheSize, 
            ref int                 cacheSize, 
            ref GlyphLookupRecord[] records,
            ref int                 recordCount, 
            ref int                 glyphCount,
            ref int                 lastLookupAdded
            )
        { 
            FontTable table = font.GetFontTable(tableTag);
 
            if (!table.IsPresent) 
            {
                return; 
            }

            FeatureList featureList;
            LookupList  lookupList; 

            Debug.Assert(tableTag == OpenTypeTags.GSUB || tableTag == OpenTypeTags.GPOS); 
 
            switch (tableTag)
            { 
                case OpenTypeTags.GSUB:
                {
                    GSUBHeader header = new GSUBHeader();
                    featureList = header.GetFeatureList(table); 
                    lookupList  = header.GetLookupList(table);
                    break; 
                } 
                case OpenTypeTags.GPOS:
                { 
                    GPOSHeader header = new GPOSHeader();
                    featureList = header.GetFeatureList(table);
                    lookupList = header.GetLookupList(table);
                    break; 
                }
                default: 
                { 
                    Debug.Assert(false);
                    featureList = new FeatureList(0); 
                    lookupList  = new LookupList(0);
                    break;
                }
            } 

            // Estimate number of records that can fit into cache using ratio of approximately 
            // 4 bytes of cache per actual record. Most of fonts will fit into this value, except 
            // some tiny caches and big EA font that can have ratio of around 5 (theoretical maximum is 8).
            // 
            // If actual ratio for particluar font will be larger than 4, we will remove records
            // from the end to fit into cache.
            //
            // If ratio is less than 4 we actually can fit more lookups, but for the speed and because most fonts 
            // will fit into cache completely anyway we do not do anything about this here.
            int maxRecordCount = maxCacheSize / 4; 
 
            // For now, we will just allocate array of maximum size.
            // Given heuristics above, it wont be greater than max cache size. 
            //
            records = new GlyphLookupRecord[maxRecordCount];

            // 
            // Now iterate through lookups and subtables, filling in lookup-glyph pairs list
            // 
            int lookupCount     = lookupList.LookupCount(table); 
            int recordCountAfterLastLookup = 0;
 
            //
            // Not all lookups can be invoked from feature directly,
            // they are actions from contextual lookups.
            // We are not interested in those, because they will 
            // never work from high level, so do not bother adding them to the cache.
            // 
            // Filling array of lookup usage bits, to skip those not mapped to any lookup 
            //
            BitArray lookupUsage = new BitArray(lookupCount); 

            for (ushort feature = 0; feature < featureList.FeatureCount(table); feature++)
            {
                FeatureTable featureTable = featureList.FeatureTable(table, feature); 

                for (ushort lookup = 0; lookup < featureTable.LookupCount(table); lookup++) 
                { 
                    ushort lookupIndex = featureTable.LookupIndex(table, lookup);
 
                    if (lookupIndex >= lookupCount)
                    {
                        // This must be an invalid font. Just igonoring this lookup here.
                        continue; 
                    }
 
                    lookupUsage[lookupIndex] = true; 
                }
            } 
            // Done with lookup usage bits

            for(ushort lookupIndex = 0; lookupIndex < lookupCount; lookupIndex++)
            { 
                if (!lookupUsage[lookupIndex])
                { 
                    continue; 
                }
 
                int firstLookupRecord   = recordCount;
                int maxLookupGlyph      = -1;
                bool cacheIsFull        = false;
 
                LookupTable lookup   = lookupList.Lookup(table, lookupIndex);
                ushort lookupType    = lookup.LookupType(); 
                ushort subtableCount = lookup.SubTableCount(); 

                for(ushort subtableIndex = 0; subtableIndex < subtableCount; subtableIndex++) 
                {
                    int subtableOffset = lookup.SubtableOffset(table, subtableIndex);

                    CoverageTable coverage = GetSubtablePrincipalCoverage(table, tableTag, lookupType, subtableOffset); 

                    if (coverage.IsInvalid) continue; 
 
                    cacheIsFull = !AppendCoverageGlyphRecords(table, lookupIndex, coverage, records, ref recordCount, ref maxLookupGlyph);
 
                    if (cacheIsFull) break;
                }

                if (cacheIsFull) break; 

                lastLookupAdded = lookupIndex; 
                recordCountAfterLastLookup = recordCount; 
            }
 
            // We may hit storage overflow in the middle of lookup. Throw this partial lookup away
            recordCount = recordCountAfterLastLookup;

            if (lastLookupAdded == -1) 
            {
                // We did not succeed adding even single lookup. 
                return; 
            }
 
            // We now have glyph records for (may be not all) lookups in the table.
            // Cache structures should be sorted by glyph, then by lookup index.
            Array.Sort(records, 0, recordCount);
 
            cacheSize  = -1;
            glyphCount = -1; 
 
            // It may happen, that records do not fit into cache, even using our heuristics.
            // We will remove lookups one by one from the end until it fits. 
            while (recordCount > 0)
            {
                CalculateCacheSize(records, recordCount, out cacheSize, out glyphCount);
 
                if (cacheSize <= maxCacheSize)
                { 
                    // Fine, we now fit into max cache size 
                    break;
                } 
                else
                {
                    // Find last lookup index
                    int lastLookup = -1; 
                    for(int i = 0; i < recordCount; i++)
                    { 
                        int lookup = records[i].Lookup; 

                        if (lastLookup < lookup) 
                        {
                            lastLookup = lookup;
                        }
                    } 

                    Debug.Assert(lastLookup >= 0); // There are lookups, so there was an index 
 
                    // Remove it
                    int currentRecord = 0; 
                    for(int i = 0; i < recordCount; i++)
                    {
                        if (records[i].Lookup == lastLookup) continue;
 
                        if (currentRecord == i) continue;
 
                        records[currentRecord] = records[i]; 
                        currentRecord++;
                    } 

                    recordCount = currentRecord;

                    // Do not forget update lastLookupAdded variable 
                    lastLookupAdded = lastLookup - 1;
                } 
            } 

            if (recordCount == 0) 
            {
                // We can't fit even single lookup into the cache
                return;
            } 

            Debug.Assert(cacheSize  > 0); // We've calcucalted them at least ones, and 
            Debug.Assert(glyphCount > 0); // if there is no records, we already should've exited 
        }
 

        /// 
        ///     Critical: unsafe pointer operations
        ///  
        [SecurityCritical]
        private static int FillTableCache( 
            IOpenTypeFont       font, 
            OpenTypeTags        tableTag,
            int                 cacheSize, 
            GlyphLookupRecord[] records,
            int                 recordCount,
            int                 glyphCount,
            int                 lastLookupAdded 
            )
        { 
            // Fill the cache. 

            // We are using basically the same code to fill the cache 
            // that had been used to calculate the size. So pList pointer
            // moving through cache memory should not overrun allocated space.
            // Asserts are set to chek that at every place where we write to cache
            // and at the end where we check that we filled exactly the same amount. 

            unsafe 
            { 
                byte[] cache = font.AllocateTableCache(tableTag, cacheSize);
                if (cache == null) 
                {
                    // We failed to allocate cache of requested size,
                    // exit without created cache.
                    return 0; 
                }
 
                fixed (byte* pCacheByte = &cache[0]) 
                {
 
                    ushort* pCache = (ushort*) pCacheByte;

                    pCache[0] = (ushort)cacheSize;              // Cache size
                    pCache[1] = 0xFFFF;                         // 0xFFFF constants 
                    pCache[2] = (ushort)(lastLookupAdded + 1);  // Number of lookups that fit into the cache
                    pCache[3] = (ushort)glyphCount;             // Glyph count 
 
                    ushort* pGlyphs = pCache + 4;
                    ushort* pList = pGlyphs + glyphCount * 2; 
                    ushort* pPrevList = null;

                    int prevListIndex = -1, prevListLength = 0;
                    int curListIndex = 0, curListLength = 1; 
                    ushort curGlyph = records[0].Glyph;
 
                    for (int i = 1; i < recordCount; i++) 
                    {
                        if (records[i].Glyph != curGlyph) 
                        {
                            // We've found another list. Compare it with previous
                            if (prevListLength != curListLength || // Fast check to avoid full comparison
                                !CompareGlyphRecordLists(records, 
                                                         recordCount,
                                                         prevListIndex, 
                                                         curListIndex) 
                               )
                            { 
                                // New list. Remember position in pPrevList and write list down
                                pPrevList = pList;

                                for (int j = curListIndex; j < i; j++) 
                                {
                                    Debug.Assert((pList - pCache) * sizeof(ushort) < cacheSize); 
                                    *pList = records[j].Lookup; 
                                    pList++;
                                } 

                                Debug.Assert((pList - pCache) * sizeof(ushort) < cacheSize);
                                *pList = 0xFFFF;
                                pList++; 
                            }
                            // Now pPrevList points at the first element of the correct list. 
 
                            *pGlyphs = curGlyph;    // Write down glyph id
                            pGlyphs++; 
                            *pGlyphs = (ushort)((pPrevList - pCache) * sizeof(ushort)); // Write down list offset
                            pGlyphs++;

                            prevListIndex = curListIndex; 
                            prevListLength = curListLength;
 
                            curGlyph = records[i].Glyph; 
                            curListIndex = i;
                            curListLength = 1; 
                        }
                    }

                    // And we need to check the last list we missed in the loop 
                    if (prevListLength != curListLength || // Fast check to avoid full comparison
                        !CompareGlyphRecordLists(records, 
                                                 recordCount, 
                                                 prevListIndex,
                                                 curListIndex) 
                       )
                    {
                        // New list. Remember position in pPrevList and write list down
                        pPrevList = pList; 

                        for (int j = curListIndex; j < recordCount; j++) 
                        { 
                            Debug.Assert((pList - pCache) * sizeof(ushort) < cacheSize);
                            *pList = records[j].Lookup; 
                            pList++;
                        }

                        Debug.Assert((pList - pCache) * sizeof(ushort) < cacheSize); 
                        *pList = 0xFFFF;
                        pList++; 
                    } 
                    // Now pPrevList points at the first element of the correct list.
 
                    *pGlyphs = curGlyph;    // Write down glyph id
                    pGlyphs++;
                    *pGlyphs = (ushort)((pPrevList - pCache) * sizeof(ushort)); // Write down list offset
                    pGlyphs++; 

                    // We are done with the cache 
                    Debug.Assert((pList - pCache) * sizeof(ushort) == cacheSize);         // We exactly filled up the cache 
                    Debug.Assert((pGlyphs - pCache) * sizeof(ushort) == (4 + glyphCount * 2) * sizeof(ushort)); // Glyphs ended where lists start.
                } 
            }

            return cacheSize;
        } 

        private static void CalculateCacheSize(GlyphLookupRecord[] records, 
                                                         int                 recordCount, 
                                                         out int             cacheSize,
                                                         out int             glyphCount 
                                                        )
        {
            // Calc cache size
            glyphCount = 1; 
            int listCount = 0;
            int entryCount = 0; 
 
            int prevListIndex = -1, prevListLength = 0;
            int curListIndex  =  0, curListLength  = 1; 
            ushort curGlyph = records[0].Glyph;

            for(int i = 1; i < recordCount; i++)
            { 
                if (records[i].Glyph != curGlyph)
                { 
                    ++glyphCount; 

                    // We've found another list. Compare it with previous 
                    if (prevListLength != curListLength || // Fast check to avoid full comparison
                        !CompareGlyphRecordLists(records,
                                                 recordCount,
                                                 prevListIndex, 
                                                 curListIndex)
                       ) 
                    { 
                        listCount++;
                        entryCount += curListLength; 
                    }

                    prevListIndex  = curListIndex;
                    prevListLength = curListLength; 

                    curGlyph = records[i].Glyph; 
                    curListIndex = i; 
                    curListLength = 1;
                } 
                else
                {
                    ++curListLength;
                } 
            }
 
            // And we need to check the last list we missed in the loop 
            if (prevListLength != curListLength || // Fast check to avoid full comparison
                !CompareGlyphRecordLists(records, 
                                         recordCount,
                                         prevListIndex,
                                         curListIndex)
               ) 
            {
                listCount++; 
                entryCount += curListLength; 
            }
 
            cacheSize = sizeof(ushort) *
                              ( 1 +                 // TotalCacheSize
                                1 +                 // Constant 0xFFFF, so we can point to it from glyphs that are not there
                                1 +                 // Number of lookups that fit into the cache 
                                1 +                 // glyph count
                                glyphCount * 2 +    // {glyphId; listOffset} per glyph 
                                entryCount +        // Each entry has lookup index 
                                listCount           // Plus, terminator entry for each list
                              ); 
        }

        private static bool CompareGlyphRecordLists(
                                                     GlyphLookupRecord[] records, 
                                                     int                 recordCount,
                                                     int                 glyphListIndex1, 
                                                     int                 glyphListIndex2 
                                                   )
        { 
            ushort listGlyph1 = records[glyphListIndex1].Glyph;
            ushort listGlyph2 = records[glyphListIndex2].Glyph;

            while (true) 
            {
                ushort glyph1,  glyph2; 
                ushort lookup1, lookup2; 

                if (glyphListIndex1 != recordCount) 
                {
                    glyph1  = records[glyphListIndex1].Glyph;
                    lookup1 = records[glyphListIndex1].Lookup;
                } 
                else
                { 
                    // Just emulate something that will be never in the real input 
                    glyph1 = 0xffff;
                    lookup1 = 0xffff; 
                }

                if (glyphListIndex2 != recordCount)
                { 
                    glyph2  = records[glyphListIndex2].Glyph;
                    lookup2 = records[glyphListIndex2].Lookup; 
                } 
                else
                { 
                    // Just emulate something that will be never in the real input.
                    glyph2 = 0xffff;
                    lookup2 = 0xffff;
                } 

                if (glyph1 != listGlyph1 && glyph2 != listGlyph2) 
                { 
                    // Both lists are ended at the same time.
                    return true; 
                }

                if (glyph1 != listGlyph1 || glyph2 != listGlyph2)
                { 
                    // One list is ended, another does not.
                    return false; 
                } 

                if (lookup1 != lookup2) 
                {
                    // We have different lookups on the lists.
                    return false;
                } 

                //Lists match so far, move further 
                ++glyphListIndex1; 
                ++glyphListIndex2;
            } 
        }

        /// 
        ///     Critical: Calls critical code 
        /// 
        [SecurityCritical] 
        private static CoverageTable GetSubtablePrincipalCoverage( 
                                                    FontTable    table,
                                                    OpenTypeTags tableTag, 
                                                    ushort       lookupType,
                                                    int          subtableOffset
                                                 )
        { 
            Debug.Assert(tableTag == OpenTypeTags.GSUB || tableTag == OpenTypeTags.GPOS);
 
            CoverageTable coverage = CoverageTable.InvalidCoverage; 

            switch (tableTag) 
            {
                case OpenTypeTags.GSUB:
                    if (lookupType == 7)
                    { 
                        ExtensionLookupTable extension =
                                new ExtensionLookupTable(subtableOffset); 
 
                        lookupType = extension.LookupType(table);
                        subtableOffset = extension.LookupSubtableOffset(table); 
                    }

                    switch (lookupType)
                    { 
                        case 1: //SingleSubst
                            SingleSubstitutionSubtable singleSubst = 
                                new SingleSubstitutionSubtable(subtableOffset); 

                            return singleSubst.GetPrimaryCoverage(table); 

                        case 2: //MultipleSubst
                            MultipleSubstitutionSubtable multipleSub =
                                new MultipleSubstitutionSubtable(subtableOffset); 
                            return multipleSub.GetPrimaryCoverage(table);
 
                        case 3: //AlternateSubst 
                            AlternateSubstitutionSubtable alternateSub =
                                new AlternateSubstitutionSubtable(subtableOffset); 
                            return alternateSub.GetPrimaryCoverage(table);

                        case 4: //Ligature subst
                            LigatureSubstitutionSubtable ligaSub = 
                                new LigatureSubstitutionSubtable(subtableOffset);
                            return ligaSub.GetPrimaryCoverage(table); 
 
                        case 5: //ContextualSubst
                            ContextSubtable contextSub = 
                                new ContextSubtable(subtableOffset);
                            return contextSub.GetPrimaryCoverage(table);

                        case 6: //ChainingSubst 
                            ChainingSubtable chainingSub =
                                                new ChainingSubtable(subtableOffset); 
                            return chainingSub.GetPrimaryCoverage(table); 

                        case 7: //Extension lookup 
                            // Ext.Lookup processed earlier. It can't contain another ext.lookups in it
                            break;

                        case 8: //ReverseCahiningSubst 
                            ReverseChainingSubtable reverseChainingSub =
                                new ReverseChainingSubtable(subtableOffset); 
                            return reverseChainingSub.GetPrimaryCoverage(table); 
                    }
 
                    break;

                case OpenTypeTags.GPOS:
                    if (lookupType == 9) 
                    {
                        ExtensionLookupTable extension = 
                                new ExtensionLookupTable(subtableOffset); 

                        lookupType = extension.LookupType(table); 
                        subtableOffset = extension.LookupSubtableOffset(table);

                    }
 
                    switch (lookupType)
                    { 
                        case 1: //SinglePos 
                            SinglePositioningSubtable singlePos =
                                new SinglePositioningSubtable(subtableOffset); 
                            return singlePos.GetPrimaryCoverage(table);

                        case 2: //PairPos
                            PairPositioningSubtable pairPos = 
                                new PairPositioningSubtable(subtableOffset);
                            return pairPos.GetPrimaryCoverage(table); 
 
                        case 3: // CursivePos
                            CursivePositioningSubtable cursivePos = 
                                new CursivePositioningSubtable(subtableOffset);
                            return cursivePos.GetPrimaryCoverage(table);

                        case 4: //MarkToBasePos 
                            MarkToBasePositioningSubtable markToBasePos =
                                new MarkToBasePositioningSubtable(subtableOffset); 
                            return markToBasePos.GetPrimaryCoverage(table); 

                        case 5: //MarkToLigaturePos 
                            // Under construction
                            MarkToLigaturePositioningSubtable markToLigaPos =
                                new MarkToLigaturePositioningSubtable(subtableOffset);
                            return markToLigaPos.GetPrimaryCoverage(table); 

                        case 6: //MarkToMarkPos 
                            MarkToMarkPositioningSubtable markToMarkPos = 
                                new MarkToMarkPositioningSubtable(subtableOffset);
                            return markToMarkPos.GetPrimaryCoverage(table); 

                        case 7: // Contextual
                            ContextSubtable contextPos =
                                new ContextSubtable(subtableOffset); 
                            return contextPos.GetPrimaryCoverage(table);
 
                        case 8: // Chaining 
                            ChainingSubtable chainingPos =
                                new ChainingSubtable(subtableOffset); 
                            return chainingPos.GetPrimaryCoverage(table);

                        case 9: //Extension lookup
                            // Ext.Lookup processed earlier. It can't contain another ext.lookups in it 
                            break;
                    } 
 
                    break;
            } 

            return CoverageTable.InvalidCoverage;
        }
 
        /// 
        /// Append lookup coverage table to the list. 
        ///  
        /// Font table
        /// Lookup index 
        /// Lookup principal coverage
        /// Record array
        /// Real number of records in record array
        /// Highest glyph index that we saw in this lookup 
        /// Returns false if we are out of list space
        ///  
        ///     Critical: Calls critical code 
        /// 
        [SecurityCritical] 
        private static bool AppendCoverageGlyphRecords(
                                                    FontTable           table,
                                                    ushort              lookupIndex,
                                                    CoverageTable       coverage, 
                                                    GlyphLookupRecord[] records,
                                                    ref int             recordCount, 
                                                    ref int             maxLookupGlyph 
                                                )
        { 
            switch (coverage.Format(table))
            {
                case 1:
                    ushort glyphCount = coverage.Format1GlyphCount(table); 

                    for(ushort i = 0; i < glyphCount; i++) 
                    { 
                        ushort glyph = coverage.Format1Glyph(table, i);
 
                        if (!AppendGlyphRecord(glyph, lookupIndex, records, ref recordCount, ref maxLookupGlyph))
                        {
                            // We've failed to add another record.
                            return false; 
                        }
                    } 
 
                    break;
 
                case 2:

                    ushort rangeCount = coverage.Format2RangeCount(table);
 
                    for(ushort i = 0; i < rangeCount; i++)
                    { 
                        ushort firstGlyph = coverage.Format2RangeStartGlyph(table, i); 
                        ushort lastGlyph  = coverage.Format2RangeEndGlyph(table, i);
 
                        for(int glyph = firstGlyph; glyph <= lastGlyph; glyph++)
                        {
                            if (!AppendGlyphRecord((ushort)glyph, lookupIndex, records, ref recordCount, ref maxLookupGlyph))
                            { 
                                // We've failed to add another record.
                                return false; 
                            } 
                        }
                    } 

                    break;
            }
 
            return true;
        } 
 
        /// 
        /// Append record to the list, but first check if we have duplicate. 
        /// 
        /// Glyph
        /// Lookup index
        /// Record array 
        /// Real number of records in record array
        /// Highest glyph index that we saw in this lookup 
        /// Returns false if we are out of list space 
        private static bool AppendGlyphRecord(
                                                ushort              glyph, 
                                                ushort              lookupIndex,
                                                GlyphLookupRecord[] records,
                                                ref int             recordCount,
                                                ref int             maxLookupGlyph 
                                            )
        { 
            if (glyph == maxLookupGlyph) 
            {
                // It is exactly max, which means we already've seen it before. 
                return true;
            }

            if (glyph > maxLookupGlyph) 
            {
                // This should be very common - coverage tables are ordered by glyph index. 
                maxLookupGlyph = glyph; 
            }
            else 
            {
                // We will go through records to check for duplicate.
                Debug.Assert(recordCount > 0); // Otherwise, we would go into (glyph > maxGlyphLookup);
                for(int i = recordCount - 1; i >= 0; i--) 
                {
                    if (records[i].Lookup != lookupIndex) 
                    { 
                        // We've iterated through all lookup records
                        // (and haven't found duplicate) 
                        break;
                    }

                    if (records[i].Glyph == glyph) 
                    {
                        // We found duplicate, no need to do anything. 
                        return true; 
                    }
                } 
            }

            // Now, we need to add new record
 
            if (recordCount == records.Length)
            { 
                // There is no space for new record. 
                return false;
            } 

            records[recordCount] = new GlyphLookupRecord(glyph, lookupIndex);
            recordCount++;
 
            return true;
        } 
 
        private class GlyphLookupRecord : IComparable
        { 
            private ushort _glyph;
            private ushort _lookup;

            public GlyphLookupRecord(ushort glyph, ushort lookup) 
            {
                _glyph = glyph; 
                _lookup = lookup; 
            }
 
            public ushort Glyph
            {
                get { return _glyph; }
            } 

            public ushort Lookup 
            { 
                get { return _lookup; }
            } 

            // Records will be sorted by glyph, then by lookup index
            public int CompareTo(GlyphLookupRecord value)
            { 
                if (_glyph < value._glyph) return -1;
                if (_glyph > value._glyph) return  1; 
 
                if (_lookup < value._lookup) return -1;
                if (_lookup > value._lookup) return 1; 

                return 0;
            }
 
            public bool Equals(GlyphLookupRecord value)
            { 
                return _glyph  == value._glyph && 
                       _lookup == value._lookup;
            } 

            public static bool operator ==(GlyphLookupRecord value1, GlyphLookupRecord value2)
            {
                return value1.Equals(value2); 
            }
            public static bool operator !=(GlyphLookupRecord value1, GlyphLookupRecord value2) 
            { 
                return !value1.Equals(value2);
            } 
            public override bool Equals(object value)
            {
                return Equals((GlyphLookupRecord)value);
            } 
            public override int GetHashCode()
            { 
                return _glyph << 16 + _lookup; 
            }
        } 

#endregion Cache filling
    }
} 

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

Link Menu

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