/// <summary>Write a TTC (TT Collection) to a disk file.</summary> public static bool WriteTTCFile(FileStream fs, OTFont[] fonts) { bool bRet = true; // build the TTC header OTTag TTCtag = (OTTag)"ttcf"; uint version = 0x00020000; uint DirectoryCount = (uint)fonts.Length; uint [] TableDirectory = new uint[fonts.Length]; uint ulDsigTag = 0; uint ulDsigLength = 0; uint ulDsigOffset = 0; uint TTCHeaderLen = 12 + DirectoryCount * 4 + 12; // length of version 2.0 header // build an array of offset tables OffsetTable[] otArr = new OffsetTable[fonts.Length]; for (int iFont = 0; iFont < fonts.Length; iFont++) { otArr[iFont] = new OffsetTable(new OTFixed(1, 0), fonts[iFont].GetNumTables()); } // build an array of head tables that will contain the updated modified field and font checksum Table_head[] arrHeadTables = new Table_head[fonts.Length]; for (int i = 0; i < fonts.Length; i++) { // get the cache Table_head headTable = (Table_head)fonts[i].GetTable("head"); Table_head.head_cache headCache = (Table_head.head_cache)headTable.GetCache(); // set the 'modified' field to the current date DateTime dt = DateTime.Now; headCache.modified = headTable.DateTimeToSecondsSince1904(dt); // generate a new table and add it to the array Table_head newHead = (Table_head)headCache.GenerateTable(); arrHeadTables[i] = newHead; } // build a list of directory entries for each font long FilePos = TTCHeaderLen; for (int iFont = 0; iFont < fonts.Length; iFont++) { ushort numTables = fonts[iFont].GetNumTables(); TableDirectory[iFont] = (uint)FilePos; FilePos += 12 + numTables * 16; uint PrevFilePos = 0; for (ushort i = 0; i < numTables; i++) { OTTable table = fonts[iFont].GetTable(i); OTTag tag = table.m_tag; if ((string)tag == "head") { table = arrHeadTables[iFont]; } // check if this table is a duplicate of a table in a previous font PrevFilePos = 0; if (iFont > 0) { for (int iPrevFont = 0; iPrevFont < iFont; iPrevFont++) { for (int iTable = 0; iTable < fonts[iPrevFont].GetNumTables(); iTable++) { OTTable PrevTable = fonts[iPrevFont].GetTable(table.m_tag); if (PrevTable != null) { if (MBOBuffer.BinaryEqual(table.m_bufTable, PrevTable.m_bufTable)) { // get the file position for the previous table for (int iDe = 0; iDe < otArr[iPrevFont].DirectoryEntries.Count; iDe++) { DirectoryEntry dePrev = (DirectoryEntry)otArr[iPrevFont].DirectoryEntries[iDe]; if (dePrev.tag == table.m_tag) { PrevFilePos = dePrev.offset; break; } } } } } } } // build a new directory entry DirectoryEntry de = new DirectoryEntry(); de.tag = new OTTag(tag.GetBytes()); de.checkSum = table.CalcChecksum(); de.length = table.GetLength(); if (PrevFilePos != 0) { de.offset = (uint)PrevFilePos; } else { de.offset = (uint)FilePos; FilePos += table.GetBuffer().GetPaddedLength(); } otArr[iFont].DirectoryEntries.Add(de); } // sort the directory entries if (numTables > 1) { for (int i = 0; i < numTables - 1; i++) { for (int j = i + 1; j < numTables; j++) { if (((DirectoryEntry)otArr[iFont].DirectoryEntries[i]).tag > ((DirectoryEntry)otArr[iFont].DirectoryEntries[j]).tag) { DirectoryEntry temp = (DirectoryEntry)otArr[iFont].DirectoryEntries[i]; otArr[iFont].DirectoryEntries[i] = (DirectoryEntry)otArr[iFont].DirectoryEntries[j]; otArr[iFont].DirectoryEntries[j] = temp; } } } } } // update each font's checksum in the head table for (int iFont = 0; iFont < fonts.Length; iFont++) { ushort numTables = fonts[iFont].GetNumTables(); // calculate the checksum uint sum = 0; sum += otArr[iFont].CalcOffsetTableChecksum(); sum += otArr[iFont].CalcDirectoryEntriesChecksum(); for (ushort i = 0; i < numTables; i++) { DirectoryEntry de = (DirectoryEntry)otArr[iFont].DirectoryEntries[i]; OTTable table = fonts[iFont].GetTable(de.tag); if ((string)de.tag == "head") { table = arrHeadTables[iFont]; } sum += table.CalcChecksum(); } // get the cache Table_head headTable = arrHeadTables[iFont]; Table_head.head_cache headCache = (Table_head.head_cache)headTable.GetCache(); // set the checkSumAdujustment field headCache.checkSumAdjustment = 0xb1b0afba - sum; // generate a new table and replace the head table in the array of head tables Table_head newHead = (Table_head)headCache.GenerateTable(); arrHeadTables[iFont] = newHead; } // write the TTC header WriteUint32MBO(fs, (uint)TTCtag); WriteUint32MBO(fs, version); WriteUint32MBO(fs, DirectoryCount); for (int i = 0; i < fonts.Length; i++) { WriteUint32MBO(fs, TableDirectory[i]); } WriteUint32MBO(fs, ulDsigTag); WriteUint32MBO(fs, ulDsigLength); WriteUint32MBO(fs, ulDsigOffset); // write out each font for (int iFont = 0; iFont < fonts.Length; iFont++) { ushort numTables = fonts[iFont].GetNumTables(); // write the offset table fs.Write(otArr[iFont].m_buf.GetBuffer(), 0, (int)otArr[iFont].m_buf.GetLength()); // write the directory entries for (int i = 0; i < numTables; i++) { DirectoryEntry de = (DirectoryEntry)otArr[iFont].DirectoryEntries[i]; fs.Write(de.m_buf.GetBuffer(), 0, (int)de.m_buf.GetLength()); } // write out each table unless a shared version has been written for (ushort i = 0; i < numTables; i++) { DirectoryEntry de = (DirectoryEntry)otArr[iFont].DirectoryEntries[i]; if (fs.Position == de.offset) { OTTable table = fonts[iFont].GetTable(de.tag); if ((string)table.m_tag == "head") { table = arrHeadTables[iFont]; } fs.Write(table.m_bufTable.GetBuffer(), 0, (int)table.GetBuffer().GetPaddedLength()); } } } return(bRet); }
/// <summary>Write a single OTF to a disk file, with tables in /// proper order, checksums set, etc. /// </summary> public static bool WriteSfntFile(FileStream fs, OTFont font) { bool bRet = true; OTFixed sfntVersion = new OTFixed(1, 0); ushort numTables = font.GetNumTables(); OffsetTable ot = new OffsetTable(sfntVersion, numTables); // order tables in fastfont order string [] arrOrderedNames = null; string [] ttNames = { "head", "hhea", "maxp", "OS/2", "hmtx", "LTSH", "VDMX", "hdmx", "cmap", "fpgm", "prep", "cvt ", "loca", "glyf", "kern", "name", "post", "gasp", "PCLT" }; string [] psNames = { "head", "hhea", "maxp", "OS/2", "name", "cmap", "post", "CFF " }; if (font.ContainsTrueTypeOutlines()) { arrOrderedNames = ttNames; } else if (font.ContainsPostScriptOutlines()) { arrOrderedNames = psNames; } OTTable[] OrderedTables = new OTTable[numTables]; for (ushort i = 0; i < numTables; i++) { OrderedTables[i] = font.GetTable(i); } if (arrOrderedNames != null) { ushort curpos = 0; for (int iName = 0; iName < arrOrderedNames.Length; iName++) { for (ushort i = curpos; i < numTables; i++) { if (arrOrderedNames[iName] == (string)OrderedTables[i].m_tag) { OTTable temp = OrderedTables[curpos]; OrderedTables[curpos] = OrderedTables[i]; OrderedTables[i] = temp; curpos++; break; } } } } // update the modified date in the head table for (int i = 0; i < OrderedTables.Length; i++) { if ((string)OrderedTables[i].m_tag == "head") { // get the cache Table_head headTable = (Table_head)OrderedTables[i]; Table_head.head_cache headCache = (Table_head.head_cache)headTable.GetCache(); // set the 'modified' field to the current date and time DateTime dt = DateTime.Now; headCache.modified = headTable.DateTimeToSecondsSince1904(dt); // generate a new table and replace the head table in the list of ordered tables Table_head newHead = (Table_head)headCache.GenerateTable(); OrderedTables[i] = newHead; break; } } // build a list of directory entries long TableFilePos = 12 + numTables * 16; for (ushort i = 0; i < numTables; i++) { OTTable table = OrderedTables[i]; OTTag tag = table.m_tag; // build a new directory entry DirectoryEntry de = new DirectoryEntry(); de.tag = new OTTag(tag.GetBytes()); de.checkSum = table.CalcChecksum(); de.offset = (uint)TableFilePos; de.length = table.GetLength(); ot.DirectoryEntries.Add(de); TableFilePos += table.GetBuffer().GetPaddedLength(); } // sort the directory entries if (numTables > 1) { for (int i = 0; i < numTables - 1; i++) { for (int j = i + 1; j < numTables; j++) { if (((DirectoryEntry)(ot.DirectoryEntries[i])).tag > ((DirectoryEntry)(ot.DirectoryEntries[j])).tag) { DirectoryEntry temp = (DirectoryEntry)ot.DirectoryEntries[i]; ot.DirectoryEntries[i] = (DirectoryEntry)ot.DirectoryEntries[j]; ot.DirectoryEntries[j] = temp; } } } } // update the font checksum in the head table for (int i = 0; i < OrderedTables.Length; i++) { if ((string)OrderedTables[i].m_tag == "head") { // calculate the checksum uint sum = 0; sum += ot.CalcOffsetTableChecksum(); sum += ot.CalcDirectoryEntriesChecksum(); for (int j = 0; j < OrderedTables.Length; j++) { sum += OrderedTables[j].CalcChecksum(); } // get the cache Table_head headTable = (Table_head)OrderedTables[i]; Table_head.head_cache headCache = (Table_head.head_cache)headTable.GetCache(); // set the checkSumAdujustment field headCache.checkSumAdjustment = 0xb1b0afba - sum; // generate a new table and replace the head table in the list of ordered tables Table_head newHead = (Table_head)headCache.GenerateTable(); OrderedTables[i] = newHead; break; } } // write the offset table fs.Write(ot.m_buf.GetBuffer(), 0, (int)ot.m_buf.GetLength()); // write the directory entries for (int i = 0; i < numTables; i++) { DirectoryEntry de = (DirectoryEntry)ot.DirectoryEntries[i]; fs.Write(de.m_buf.GetBuffer(), 0, (int)de.m_buf.GetLength()); } // write the tables for (ushort i = 0; i < numTables; i++) { OTTable table = OrderedTables[i]; fs.Write(table.m_bufTable.GetBuffer(), 0, (int)table.GetBuffer().GetPaddedLength()); } return(bRet); }