/// <summary> /// Append record to the list, but first check if we have duplicate. /// </summary> /// <param name="glyph">Glyph</param> /// <param name="lookupIndex">Lookup index</param> /// <param name="records">Record array</param> /// <param name="recordCount">Real number of records in record array</param> /// <param name="maxLookupGlyph">Highest glyph index that we saw in this lookup</param> /// <returns>Returns false if we are out of list space</returns> 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 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 }