Ejemplo n.º 1
0
        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
        }