// Reads through the SVG table copying over all of the SVG content into individual .svg files and writes those files into // a folder of the name, outputFolder public int ExportSvgContent(TableRecord rt, byte[] source, StorageFolder outputFolder) { int numberSvgs = 0; start = rt.offset; // start always refer to the offset to the file head. current = rt.offset; // Read SVG Main Header ushort version = ReadDataAndIncreaseIndex <ushort>(source, ref current, sizeof(ushort)); uint svgDocIndexOffset = ReadDataAndIncreaseIndex <uint>(source, ref current, sizeof(uint)); uint reserved = ReadDataAndIncreaseIndex <uint>(source, ref current, sizeof(uint)); // Read document Index uint documentIndexOffset = start + svgDocIndexOffset; ushort numEntries = ReadDataAndIncreaseIndex <ushort>(source, ref documentIndexOffset, sizeof(ushort)); SVGDocIdxEntry[] docIdxEntries = new SVGDocIdxEntry[numEntries]; for (int i = 0; i < docIdxEntries.Length; i++) { docIdxEntries[i] = ReadSvgDocIdxEntry(source, ref documentIndexOffset); string filename = docIdxEntries[i].startID.ToString(); byte[] svgContent = new byte[docIdxEntries[i].docLength]; Array.Copy(source, (int)(docIdxEntries[i].docOffset + rt.offset + svgDocIndexOffset), svgContent, 0, svgContent.Length); WriteSvgContent(outputFolder, filename, svgContent); numberSvgs++; } return(numberSvgs); }
// Parses font's table record and alters TableRecord object appropriately. // Also keeps track of the location of the offset for that table record. public TableRecord ReadTableRecord(byte[] source) { TableRecord record = new TableRecord(); record.tagInt = ReadData <uint>(source, ref start, sizeof(uint)); record.tag = ReadDataAndIncreaseIndex(source, ref start, TagLength); record.checksum = ReadDataAndIncreaseIndex <uint>(source, ref start, sizeof(uint)); // Not a part of table records---just saved and used in ChangeTableRecOffsets // to change TableRecordOffsets easily. record.offsetOfOffset = start; record.offset = ReadDataAndIncreaseIndex <uint>(source, ref start, sizeof(uint)); record.length = ReadDataAndIncreaseIndex <uint>(source, ref start, sizeof(uint)); return(record); }
// Parse cmap table from the font // Only parses cmap entries with format IDs of 0, 4, 6, or 12 (covers the vast majority of cases) public List <GlyphModel> ParseCMap(TableRecord tableRecord, byte[] source) { CmapHeader header = new CmapHeader(); List <GlyphModel> allChars = new List <GlyphModel>(); start = tableRecord.offset; header.version = ReadDataAndIncreaseIndex <ushort>(source, ref start, sizeof(ushort)); header.numTables = ReadDataAndIncreaseIndex <ushort>(source, ref start, sizeof(ushort)); for (int i = 0; i < header.numTables; i++) { CmapEncodingRecord record = new CmapEncodingRecord(); record.platformID = ReadDataAndIncreaseIndex <ushort>(source, ref start, sizeof(ushort)); record.encodingID = ReadDataAndIncreaseIndex <ushort>(source, ref start, sizeof(ushort)); record.offset = ReadDataAndIncreaseIndex <uint>(source, ref start, sizeof(uint)); uint prevPos = start; start = record.offset + tableRecord.offset; ushort formatID = ReadData <ushort>(source, ref start, sizeof(ushort)); switch (formatID) { case 0: ParseFormat0(source, ref start, allChars); break; case 4: ParseFormat4(source, ref start, allChars); break; case 6: ParseFormat6(source, ref start, allChars); break; case 12: ParseFormat12(source, ref start, allChars); break; default: break; } start = prevPos; } return(allChars); }
// Reads through the Name Record Table and saves each of the Name Records in an array. // Loops through the nameRecord array to find a record with a nameID == 1 and a length > 0. // If such a nameRecord is found, the family name is read and returned. If no such // nameRecord is found, returns null. public string GetFamilyName(TableRecord record, byte[] source) { start = record.offset; current = record.offset; ushort format = ReadDataAndIncreaseIndex <ushort>(source, ref current, sizeof(ushort)); ushort count = ReadDataAndIncreaseIndex <ushort>(source, ref current, sizeof(ushort)); ushort stringOffset = ReadDataAndIncreaseIndex <ushort>(source, ref current, sizeof(ushort)); NameRecord[] nameRecords = new NameRecord[count]; for (int i = 0; i < nameRecords.Length; i++) { nameRecords[i] = ReadNameRecordEntry(source, ref current); } for (int i = 0; i < nameRecords.Length; i++) { if (nameRecords[i].nameID == 1 && nameRecords[i].length > 0) { uint index = current + nameRecords[i].offset; return(ReadDataAndIncreaseIndexForFamilyName(source, ref index, nameRecords[i].length)); } } return(null); }
// Adds the SVG defined by svgContent to the font file at the specified glyphID public void AssembleSvgContent(ref TableRecord record, ref byte[] source, byte[] svgContent, ushort glyphID, FontModel fontModel) { start = record.offset; // start always refer to the offset to the file head. current = record.offset; // Read SVG Main Header ushort version = ReadDataAndIncreaseIndex <ushort>(source, ref current, sizeof(ushort)); uint svgDocIndexOffset = ReadDataAndIncreaseIndex <uint>(source, ref current, sizeof(uint)); uint reserved = ReadDataAndIncreaseIndex <uint>(source, ref current, sizeof(uint)); // Read document Index uint documentIndexOffset = start + svgDocIndexOffset; uint svgDocIndexAbsoluteOffset = start + svgDocIndexOffset; ushort numEntries = ReadDataAndIncreaseIndex <ushort>(source, ref documentIndexOffset, sizeof(ushort)); int svgLength; int paddingDiff = 0; long diff; byte[] recordLength; byte[] newSource; byte[] checksum; uint newDocumentIndexOffset = 0; uint newPadding = 0; uint currPadding = CalculatePadding(record.length); bool firstPass = true; SVGDocIdxEntry[] docIdxEntries = new SVGDocIdxEntry[numEntries]; for (int i = 0; i < docIdxEntries.Length; i++) { docIdxEntries[i] = ReadSvgDocIdxEntry(source, ref documentIndexOffset); // Case 0: takes into account the glyphID already having SVG content if (docIdxEntries[i].startID.Equals(glyphID)) { svgLength = svgContent.Length; diff = docIdxEntries[i].docLength - svgLength; uint svgOffset = docIdxEntries[i].docOffset + record.offset + svgDocIndexOffset; recordLength = GetBytesBigEndian((uint)(record.length - diff)); // Changes the SVG table record length by the difference between the length of the old SVG content and the new SVG content Array.Copy(recordLength, 0, source, (int)record.offsetOfOffset + sizeof(uint), sizeof(uint)); newPadding = CalculatePadding((uint)(record.length - diff)); paddingDiff = (int)currPadding - (int)newPadding; byte[] length = GetBytesBigEndian((uint)svgLength); // Changes the length of the SVG content contained in the SVG Document Index Entry to reflect the length of the new SVG content Array.Copy(length, 0, source, (int)(documentIndexOffset - sizeof(uint)), sizeof(uint)); // Change the table record offsets for tables following the altered SVG content to reflect the change in length ChangeTableRecOffsets(fontModel.TableRecords, ref source, (diff + paddingDiff), start + svgDocIndexOffset); // Changes the SVG offsets in the SVG Document Index Entries for the SVG content that was written after the altered SVG content in the // font file to reflect a shift of the difference in length between the old and new SVG content ChangeSvgOffsets(fontModel.TableRecords, ref source, diff, docIdxEntries[i].docOffset); newSource = new byte[source.Length - (diff + paddingDiff)]; // Copies from the beginning of source to the beginning of the altered SVG content Array.Copy(source, 0, newSource, 0, (int)svgOffset); // Skips the length of the new SVG content in the newSource array and copies from the beginning of the SVG content following the altered SVG content // to the end of the SVG Table Array.Copy(source, (int)(svgOffset + docIdxEntries[i].docLength), newSource, (int)svgOffset + svgLength, (int)(record.offset + record.length - svgOffset - docIdxEntries[i].docLength)); // Makes sure the padding at the end of the SVG table in the newSource array is correct and copies from the beginning o Array.Copy(source, (int)(record.offset + record.length + currPadding), newSource, (int)(svgLength + record.offset + record.length - docIdxEntries[i].docLength + newPadding), (int)(source.Length - (record.offset + record.length + currPadding))); // Copies from the beginning of the table following the SVG Table (after the entire SVG table and it's padding) to the end of the font file Array.Copy(svgContent, 0, newSource, (int)svgOffset, svgLength); source = newSource; record.length = (uint)(record.length - diff); checksum = GetBytesBigEndian(CalcTableChecksum(record.offset, record.length, ref source)); Array.Copy(checksum, 0, source, (int)(record.offsetOfOffset - sizeof(uint)), sizeof(uint)); record.checksum = BitConverter.ToUInt32(checksum, 0); return; } // Case 1: takes into account the glyphID not having SVG content and not being the last glyphID else if (docIdxEntries[i].startID > glyphID && firstPass) { // newDocumentIndexOffset stores the location that the new SVG Document Index Entry would have to be added newDocumentIndexOffset = documentIndexOffset - (uint)Marshal.SizeOf <SVGDocIdxEntry>(); firstPass = false; break; } } // Case 2: takes into account the glyphID not having SVG content and being the last glyphID if (firstPass) { // newDocumentIndexOffset stores the location that the new SVG Document Index Entry would have to be added // In this case right after the last SVG Document Index Entry newDocumentIndexOffset = documentIndexOffset; } svgLength = svgContent.Length; // In cases 1 and 2 an entirely new SVG Document Index Entry must be added, so diff is the length of the SVG content plus the length of an // SVG Document Index Entry // This is made negative because for consistency's sake diff is always subtracted diff = (svgLength + Marshal.SizeOf <SVGDocIdxEntry>()) * -1; recordLength = GetBytesBigEndian((uint)(record.length - diff)); // Changes the SVG table record length by diff Array.Copy(recordLength, 0, source, (int)record.offsetOfOffset + sizeof(uint), sizeof(uint)); newPadding = CalculatePadding((uint)(record.length - diff)); paddingDiff = (int)currPadding - (int)newPadding; // Change the table record offsets for tables following the added SVG content to reflect the change in length ChangeTableRecOffsets(fontModel.TableRecords, ref source, (diff + paddingDiff), start + svgDocIndexOffset); // Brand new SVG content is being added to the very end of the SVG table, so the offsets contained in the SVG // Document Index Entries need only be changed by the length of the new SVG Document Index Entry that will be addded ChangeSvgOffsets(fontModel.TableRecords, ref source, diff + svgLength, 0); uint numEntriesOffset = start + svgDocIndexOffset; byte[] newNumEntries = GetBytesBigEndian((ushort)(numEntries + 1)); // Increments the number of entries contained in the SVG document index table Array.Copy(newNumEntries, 0, source, (int)numEntriesOffset, sizeof(ushort)); newSource = new byte[source.Length - (diff + paddingDiff)]; // Copies from the begining of source to the end of the SVG Document Index Entry preceeding the one to be added Array.Copy(source, 0, newSource, 0, (int)newDocumentIndexOffset); // Assumes the start and end glyphIDs are the same because currently you can only add one SVG at a time byte[] startAndEndGlyphID = GetBytesBigEndian(glyphID); int newSourceCopyOffset = (int)newDocumentIndexOffset; // Copies the startGlyphID to the newSource array Array.Copy(startAndEndGlyphID, 0, newSource, newSourceCopyOffset, sizeof(ushort)); newSourceCopyOffset += sizeof(ushort); // Copies the endGlyphID to the newSource array Array.Copy(startAndEndGlyphID, 0, newSource, newSourceCopyOffset, sizeof(ushort)); newSourceCopyOffset += sizeof(ushort); byte[] svgDocOffset = GetBytesBigEndian((uint)(record.length - svgDocIndexOffset + Marshal.SizeOf <SVGDocIdxEntry>())); // Copies the svgDocOffset to the newSource array Array.Copy(svgDocOffset, 0, newSource, newSourceCopyOffset, sizeof(uint)); newSourceCopyOffset += sizeof(uint); byte[] svgDocLength = GetBytesBigEndian((uint)svgLength); // Copies the svgDocLength to the newSource array Array.Copy(svgDocLength, 0, newSource, newSourceCopyOffset, sizeof(uint)); newSourceCopyOffset += sizeof(uint); // Copies from the begining of the SVG Document Index Entry following the one added to the end of the SVG Table in source Array.Copy(source, (int)newDocumentIndexOffset, newSource, newSourceCopyOffset, (int)(record.length - svgDocIndexOffset + svgDocIndexAbsoluteOffset - newDocumentIndexOffset)); newSourceCopyOffset += (int)(record.length - svgDocIndexOffset + svgDocIndexAbsoluteOffset - newDocumentIndexOffset); // Copies the new SVG content to newSource Array.Copy(svgContent, 0, newSource, newSourceCopyOffset, svgLength); // Makes sure the padding on the SVG Table remains correct newSourceCopyOffset += (int)(svgLength + newPadding); // Copies from the beginning of the table following the SVG table to the end of the file Array.Copy(source, (int)svgDocIndexAbsoluteOffset + (int)(record.length - svgDocIndexOffset + currPadding), newSource, newSourceCopyOffset, source.Length - ((int)svgDocIndexAbsoluteOffset + (int)(record.length - svgDocIndexOffset + currPadding))); source = newSource; record.length = (uint)(record.length - diff); checksum = GetBytesBigEndian(CalcTableChecksum(record.offset, record.length, ref source)); Array.Copy(checksum, 0, source, (int)(record.offsetOfOffset - sizeof(uint)), sizeof(uint)); record.checksum = BitConverter.ToUInt32(checksum, 0); }
// Removes SVG of the specified glyph ID from the SVG table of the font. public void RemoveSvgContent(ref TableRecord record, ref byte[] source, ushort glyphID, FontModel fontModel) { start = record.offset; // Start always refers to the offset to the file head. current = record.offset; // Read SVG main header. ushort version = ReadDataAndIncreaseIndex <ushort>(source, ref current, sizeof(ushort)); uint svgDocIndexOffset = ReadDataAndIncreaseIndex <uint>(source, ref current, sizeof(uint)); uint reserved = ReadDataAndIncreaseIndex <uint>(source, ref current, sizeof(uint)); // Read document index. uint documentIndexOffset = start + svgDocIndexOffset; uint svgDocIndexAbsoluteOffset = start + svgDocIndexOffset; ushort numEntries = ReadDataAndIncreaseIndex <ushort>(source, ref documentIndexOffset, sizeof(ushort)); uint currPadding = CalculatePadding(record.length); SVGDocIdxEntry[] docIdxEntries = new SVGDocIdxEntry[numEntries]; for (int i = 0; i < docIdxEntries.Length; i++) { docIdxEntries[i] = ReadSvgDocIdxEntry(source, ref documentIndexOffset); // Checks that SVG Document Index Entry matches the one to remove. // Note: Currently only supports one-to-one SVG/glyph ID mapping. if (docIdxEntries[i].startID.Equals(glyphID)) { int svgLength = (int)docIdxEntries[i].docLength; // diff calculates the change in length of the font file because of removing the SVG. // diff = length of the SVG content to remove + length of the SVG document index entry long diff = svgLength + Marshal.SizeOf <SVGDocIdxEntry>(); uint svgOffset = docIdxEntries[i].docOffset + record.offset + svgDocIndexOffset; // Offset to the beginning of the SVG content // Edits the SVG table length stored in the table record for SVG to the current length - diff byte[] recordLength = GetBytesBigEndian((uint)(record.length - diff)); Array.Copy(recordLength, 0, source, (int)record.offsetOfOffset + sizeof(uint), sizeof(uint)); uint newPadding = CalculatePadding((uint)(record.length - diff)); int paddingDiff = (int)currPadding - (int)newPadding; // Change the table record offsets for tables following the removed SVG content ChangeTableRecOffsets(fontModel.TableRecords, ref source, (diff + paddingDiff), start + svgDocIndexOffset); // Changes all of the SVG offsets in the SVG Document Index Entries to reflect an upward shift of 12 as caused by the removal of // the specified SVG Document Index Entry ChangeSvgOffsets(fontModel.TableRecords, ref source, diff - svgLength, 0); // Changes the SVG offsets in the SVG Document Index Entries for the SVG content that was written after the removed SVG content in the // font file to reflect an upward shift of the length of the removed SVG content ChangeSvgOffsets(fontModel.TableRecords, ref source, diff - Marshal.SizeOf <SVGDocIdxEntry>(), docIdxEntries[i].docOffset); uint numEntriesOffset = start + svgDocIndexOffset; // Decrements the number specifying the number of SVG entries and edits font appropriately byte[] newNumEntries = GetBytesBigEndian((ushort)(numEntries - 1)); Array.Copy(newNumEntries, 0, source, (int)numEntriesOffset, sizeof(ushort)); // Creates byte array for the corrected font and copies over font while removing SVG content and // changing the padding of the SVG table appropriately byte[] newSource = new byte[source.Length - (diff + paddingDiff)]; // Copies from the beginning of source to the end of the SVG Document Index Entry preceeding the one to be removed int sourceIndex = 0; int newSourceIndex = 0; int length = (int)(documentIndexOffset - Marshal.SizeOf <SVGDocIdxEntry>()); Array.Copy(source, sourceIndex, newSource, newSourceIndex, length); // Skips the DocIdxEntry to remove and copies from the beginning of the SVG DocIdxEntry following the removed one to the end of the SVG content preceeding the removed SVG content sourceIndex += (int)(documentIndexOffset); newSourceIndex += length; length = (int)(svgDocIndexAbsoluteOffset + docIdxEntries[i].docOffset - documentIndexOffset); Array.Copy(source, sourceIndex, newSource, newSourceIndex, length); // Copies from the beginning of the SVG content following the SVG content to remove until the end of the SVG table // Stops copying at this point in order to make sure the padding at the end of the SVG table is correct sourceIndex += length + (int)docIdxEntries[i].docLength; newSourceIndex += length; length = (int)(record.offset + record.length - (svgDocIndexAbsoluteOffset + docIdxEntries[i].docOffset + docIdxEntries[i].docLength)); Array.Copy(source, sourceIndex, newSource, newSourceIndex, length); // Copies from the beginning of the table following the SVG Table (after the entire SVG table and its padding) to the end of the font file sourceIndex = (int)(record.offset + record.length + currPadding); newSourceIndex = (int)(record.offset + record.length - docIdxEntries[i].docLength - Marshal.SizeOf <SVGDocIdxEntry>() + newPadding); length = (int)(source.Length - (record.offset + record.length + currPadding)); Array.Copy(source, sourceIndex, newSource, newSourceIndex, length); // Set the new source. source = newSource; // Alters the TableRecord object to reflect the new length of the table record.length = (uint)(record.length - diff); // Edits checksum of SVG table byte[] checksum = GetBytesBigEndian(CalcTableChecksum(record.offset, record.length, ref source)); Array.Copy(checksum, 0, source, (int)(record.offsetOfOffset - sizeof(uint)), sizeof(uint)); record.checksum = BitConverter.ToUInt32(checksum, 0); return; } } }
public async Task AddSvgAsync(ushort glyphID, StorageFile svgFile) { // Add SVG data into a buffer. IBuffer buffer = await FileIO.ReadBufferAsync(svgFile); byte[] svgContent = new byte[buffer.Length]; // Read buffer into bytes in svgContent. using (DataReader reader = DataReader.FromBuffer(buffer)) { reader.ReadBytes(svgContent); } // Tweak the SVG: Add glyph ID and adjust the X/Y origin in accordance with OpenType spec. ChangeSvgOriginID(ref svgContent, glyphID); for (int i = 0; i < TableRecords.Length; i++) { if (TableRecords[i].tag == "SVG ") { parser.AssembleSvgContent(ref TableRecords[i], ref source, svgContent, glyphID, this); break; } // Checks if SVG table is not present else if (String.Compare(TableRecords[i].tag, "SVG ", StringComparison.Ordinal) > 0) { // Changes offset table to reflect the addition of a new table offsetTable = parser.UpdateOffsetTable(offsetTable, ref source); // Changes the offsets contained in all of the table records in order to reflect the addition of a table record parser.ChangeTableRecOffsets(TableRecords, ref source, ((sizeof(uint) * 4) * -1), 0); byte[] newSource = new byte[source.Length + (sizeof(uint) * 6) + (sizeof(ushort) * 2)]; // Copies from the beginning of the source array to the end of the table record preceeding the location of the new SVG table record Array.Copy(source, 0, newSource, 0, (int)(TableRecords[i].offsetOfOffset - (sizeof(uint) * 2))); string tag = "SVG "; byte[] svgTag = Encoding.ASCII.GetBytes((string)tag); // Adds SVG tag for the table record Array.Copy(svgTag, 0, newSource, (int)(TableRecords[i].offsetOfOffset - (sizeof(uint) * 2)), sizeof(uint)); byte[] checksum = BitConverter.GetBytes((uint)0); Array.Reverse(checksum); // Adds the checksum for the table record Array.Copy(checksum, 0, newSource, (int)(TableRecords[i].offsetOfOffset - sizeof(uint)), sizeof(uint)); byte[] offset = BitConverter.GetBytes((uint)(source.Length + (sizeof(uint) * 4))); Array.Reverse(offset); // Adds the offset for the table record Array.Copy(offset, 0, newSource, (int)(TableRecords[i].offsetOfOffset), sizeof(uint)); byte[] length = BitConverter.GetBytes((uint)((sizeof(ushort) + sizeof(uint)) * 2)); Array.Reverse(length); // Adds the length for the table record Array.Copy(length, 0, newSource, (int)(TableRecords[i].offsetOfOffset + sizeof(uint)), sizeof(uint)); // Copies from the beginning of the table record following the new SVG table record in the source array to the end of the source array Array.Copy(source, (int)(TableRecords[i].offsetOfOffset - (sizeof(uint) * 2)), newSource, (int)(TableRecords[i].offsetOfOffset + (sizeof(uint) * 2)), (int)(source.Length - (TableRecords[i].offsetOfOffset - (sizeof(uint) * 2)))); byte[] version = BitConverter.GetBytes((ushort)0); Array.Reverse(version); // Adds the SVG table at the very end of the font file // Copies the version number to the newSource array (0) Array.Copy(version, 0, newSource, (int)(source.Length + (sizeof(uint) * 4)), sizeof(ushort)); byte[] svgDocIndexOffset = BitConverter.GetBytes((uint)(sizeof(ushort) + (sizeof(uint) * 2))); Array.Reverse(svgDocIndexOffset); // Copies the SVG Document Index Offset to the newSource array Array.Copy(svgDocIndexOffset, 0, newSource, (int)(source.Length + (sizeof(uint) * 4) + sizeof(ushort)), sizeof(uint)); byte[] reserved = BitConverter.GetBytes((uint)0); Array.Reverse(reserved); // Copies reserved to th enewSource array Array.Copy(reserved, 0, newSource, (int)(source.Length + (sizeof(uint) * 5) + sizeof(ushort)), sizeof(uint)); byte[] numEntries = BitConverter.GetBytes((ushort)0); Array.Reverse(numEntries); // Copies the number of SVG Document Index Entries to the newSource array (in this case just 0 because it is a new table) Array.Copy(numEntries, 0, newSource, (int)(source.Length + (sizeof(uint) * 6) + sizeof(ushort)), sizeof(ushort)); TableRecord svg = new TableRecord(); svg.tag = "SVG "; svg.checksum = 0; svg.offsetOfOffset = TableRecords[i].offsetOfOffset; svg.offset = (uint)(source.Length + (sizeof(uint) * 4)); svg.length = (uint)((sizeof(ushort) + sizeof(uint)) * 2); source = newSource; parser.Start = 12; // Start is 12 because the offset table is always before the table records and is always 12 bytes in length parser.Current = 12; TableRecords = new TableRecord[offsetTable.numTables]; for (int j = 0; j < TableRecords.Length; j++) { TableRecords[j] = parser.ReadTableRecord(source); } parser.AssembleSvgContent(ref svg, ref source, svgContent, glyphID, this); break; } } // Create an updated font file in temp folder. StorageFolder tempFolder = ApplicationData.Current.TemporaryFolder; StorageFile newFontFile = await tempFolder.CreateFileAsync(FontFileName, CreationCollisionOption.GenerateUniqueName); await FileIO.WriteBytesAsync(newFontFile, source); // Hang on to the new font file. temporaryFontFile = newFontFile; }