This blog is based on the ttf definition which I found here
Simply put, the file starts with an offset table which includes the number of table structures that follow it. Each table structure has a tag and we are going to find the table structure with the "name" tag. This table structure points to a TableHeader which contains a pointer to a number of name records. We read those name records and decode them, displaying the results to the user. We're only going to process the name records that are in English.
The XAML is simply a datagrid.
<Window x:Class="TtfDecode.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TtfDecode"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<DataGrid ItemsSource="{Binding FontProperties}" IsReadOnly="True" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}" Width="80"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="*"/>
<DataGridTextColumn Header="Value" Binding="{Binding Value}" Width="2*"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Windows;
namespace TtfDecode
{
class cOffsetTable
{
public ushort
uMajorVersion;
public ushort
uMinorVersion;
public ushort
uNumOfTables;
public ushort
uSearchRange;
public ushort
uEntrySelector;
public ushort
uRangeShift;
public void
Load(cBinaryContent BinaryContent)
{
uMajorVersion =
BinaryContent.ReadUShort();
uMinorVersion = BinaryContent.ReadUShort();
uNumOfTables =
BinaryContent.ReadUShort();
uSearchRange =
BinaryContent.ReadUShort();
uEntrySelector =
BinaryContent.ReadUShort();
uRangeShift =
BinaryContent.ReadUShort();
}
}
class cTableDirectory
{
public string szTag;
public ulong
uCheckSum;
public ulong uOffset;
public ulong uLength;
public void
Load(cBinaryContent BinaryContent)
{
szTag =
BinaryContent.ReadString(4);
uCheckSum =
BinaryContent.ReadULong();
uOffset =
BinaryContent.ReadULong();
uLength =
BinaryContent.ReadULong();
}
}
class cTableHeader
{
public ushort
uFSelector;
public ushort
uNRCount;
public ushort
uStorageOffset;
public void
Load(cBinaryContent BinaryContent)
{
uFSelector =
BinaryContent.ReadUShort();
uNRCount =
BinaryContent.ReadUShort();
uStorageOffset =
BinaryContent.ReadUShort();
}
}
class cNameRecord
{
public ushort
uPlatformID;
public ushort
uEncodingID;
public ushort
uLanguageID;
public ushort uNameID;
public ushort
uStringLength;
public ushort
uStringOffset;
public void
Load(cBinaryContent BinaryContent)
{
uPlatformID =
BinaryContent.ReadUShort();
uEncodingID =
BinaryContent.ReadUShort();
uLanguageID =
BinaryContent.ReadUShort();
uNameID =
BinaryContent.ReadUShort();
uStringLength = BinaryContent.ReadUShort();
uStringOffset =
BinaryContent.ReadUShort();
}
}
class cBinaryContent
{
private byte[]
Content;
private ulong _p = 0;
public ulong p
{
get { return _p; }
set
{
if (value >= 0 && value < (ulong)Content.Length)
_p = value;
else
throw new Exception(string.Format("Invalid value for content offset {0}", p));
}
}
public void
LoadFromFile(String FileName)
{
Content =
File.ReadAllBytes(FileName);
p = 0;
}
public byte
ReadByte()
{
Byte b = Content[p];
p += 1;
return b;
}
public ushort
ReadUShort()
{
return (ushort)(ReadByte()
* 256 + ReadByte());
}
public ulong
ReadULong()
{
return (ulong)(ReadByte()
* 16777216 + ReadByte() * 65536 + ReadByte() * 256 + ReadByte());
}
public string
ReadString(ulong count)
{
String s = "";
foreach (byte b in Content.Skip((int)p).Take((int)count))
{
if (b != 0) s += (char)b;
}
p += count;
return s;
}
}
public class cFontProperty
{
public ushort ID { get; set; }
public String Name { get; set; }
public string Value { get; set; }
}
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<cFontProperty> _FontProperties = new
ObservableCollection<cFontProperty>();
public ObservableCollection<cFontProperty> FontProperties
{
get { return
_FontProperties; }
set
{
_FontProperties = value;
PropChanged("FontProperties");
}
}
public MainWindow()
{
InitializeComponent();
DecodeTTF(@"C:\Windows\Fonts\webdings.ttf");
}
public void
DecodeTTF(String FileName)
{
cBinaryContent BinaryContent = new cBinaryContent();
cOffsetTable OffsetTable = new cOffsetTable();
List<cTableDirectory>
TableDirectories = new
List<cTableDirectory>();
cTableDirectory NameTable = null;
cTableHeader NameHeader = new cTableHeader();
List<cNameRecord> NameRecords
= new List<cNameRecord>();
BinaryContent.LoadFromFile(FileName);
OffsetTable.Load(BinaryContent);
for (int i = 0; i
< OffsetTable.uNumOfTables; i++)
{
cTableDirectory td = new cTableDirectory();
td.Load(BinaryContent);
if (td.szTag == "name") NameTable = td;
}
if (NameTable != null)
{
BinaryContent.p =
NameTable.uOffset;
NameHeader.Load(BinaryContent);
for (int i = 0; i
< NameHeader.uNRCount; i++)
{
cNameRecord nr = new cNameRecord();
nr.Load(BinaryContent);
NameRecords.Add(nr);
}
}
// decode the
english name records
foreach (cNameRecord nr in NameRecords.Where(n => n.uLanguageID == 0))
{
String PropertyName = "";
String PropertyValue = "";
BinaryContent.p =
NameTable.uOffset + nr.uStringOffset + NameHeader.uStorageOffset;
switch (nr.uNameID)
{
case 0: PropertyName = "Copyright Notice"; break;
case 1: PropertyName = "Font Family"; break;
case 2: PropertyName = "Font Subfamily"; break;
case 3: PropertyName = "Unique subfamily
identification"; break;
case 4: PropertyName = "Full name"; break;
case 5: PropertyName = "Version"; break;
case 6: PropertyName = "Postscript name"; break;
case 7: PropertyName = "Trademark"; break;
case 8: PropertyName = "Manufacturer"; break;
case 9: PropertyName = "Designer"; break;
case 10: PropertyName = "Description"; break;
case 11: PropertyName = "Vendor URL"; break;
case 12: PropertyName = "Designer URL"; break;
case 13: PropertyName = "License"; break;
case 14: PropertyName = "License URL"; break;
case 16: PropertyName = "Preferred Family"; break;
case 17: PropertyName = "Preferred Subfamily"; break;
case 19: PropertyName = "Sample text"; break;
}
PropertyValue =
BinaryContent.ReadString(nr.uStringLength);
if (PropertyName != "" && PropertyValue != "")
FontProperties.Add(new cFontProperty() { ID = nr.uNameID,
Name = PropertyName, Value = PropertyValue });
}
}
public event
PropertyChangedEventHandler PropertyChanged;
public void
PropChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
No comments:
Post a Comment