// 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; } } }