ODTTF の秘密

Last Updated 2011/09/21


XPS ファイルの秘密」のページの Resources/Fonts フォルダに .ODTTF ファイルがあることを紹介しました。これは XPS ドキュメント内で使用するフォントのサブセットです。このページでは ODTTF ファイルとは何か、また、このファイルからフォント名(WPF 的にはフォントファミリ名ですが)を取得する方法について説明します。


ODTTF は "Obfuscated True Type Font" の略と思われます。"Obfuscated" は「分かりにくくした」でいいと思います。要するに、XPS ドキュメント内で使用するフォントの中には著作権がハッキリしていて、自由に使われては困る場合があります。そこで、チョットだけ分かりにくくしようとするものです。

「チョットだけ」の意味は、仕様書としての XPS の中に分かりにくくするための手順が明記されていますので、その手順を逆にたどれば本物のフォントデータを取り出すことができるからです。フォントデータを暗号化するわけではないので、これぐらいの制限ならやめとけばいいのに思わないでもありません。

さて、ODTTF ファイルはフォントのサブセットと説明しましたが、サブセットとはすべての文字ではなく、実際に XPS ドキュメント内で使用する文字だけのセットという意味です。たとえば、「あいうえお」だけを使用する XPS ドキュメントの ODTTF ファイルは「あいうえお」の 5 文字のフォントデータしか含みません。


ODTTF ファイルの "Obfuscated" を解消する

"Obfuscated" にする手順は仕様書としての XPS に規定されているとすでに説明しましたが、実は WPF の中に "Obfuscated" にする機能があります。XpsFont クラスの ObfuscatedFontData メソッドです。

Note WPF の各クラスのプロパティやメソッドについては WPF クラスライブラリリファレンスを参照してください。

ODTTF ファイルのファイル名は、"fe7c144c-7589-4240-bc1d-e2979bf67e2d.ODTTF" のようなものですが、拡張子を除くファイル名の部分が GUID そのものになっています。つまり、ObfuscatedFontData メソッドの引数 guid にこれを設定します。

さて、仕様書としての XPS の 2.1.7.3 Embedded Font Obfuscation の中に "Obfuscated" するための手順の説明があります。それによると、"Obfuscated" するフォントファイルの先頭の 16 バイトおよび次の 16 バイト、計 32 バイトに GUID の各バイトを XOR 演算します。XOR 演算は同じ計算を 2 回実行すると元に戻るという性質がありますので、元のデータを復元することは簡単です。決して、暗号化ではありません。

文章でダラダラと書いても分かりにくいので、コードを使って説明します。ODTTF ファイル名を与えてフォントファミリ名を取得します。一旦、TTF ファイルに出力するなどスマートさには欠けますが、.Net Framework の機能の範囲内ではほかに手段はないと思います。

// ODTTF ファイルから FontFamily 名を取得する
// fileName : ODTTF ファイル名
private string GetFontFamilyNameFromODTTF(string fileName)
{
  string TEMP = "temp.ttf";
  TEMP = Path.Combine(Directory.GetCurrentDirectory(), TEMP);

  Guid guid = new Guid(Path.GetFileNameWithoutExtension(fileName));
  FileStream stream = new FileStream(fileName, FileMode.Open);
  BinaryReader reader = new BinaryReader(stream);

  // ODTTF ファイルの内容を読み込む
  byte[] buffer = reader.ReadBytes((int)stream.Length);

  // Obfuscated を解消する
  DeobfuscateData(buffer, guid);

  reader.Close();

  // TTF ファイルとして出力する
  FileStream stream2 = new FileStream(TEMP, FileMode.Create);
  stream2.Write(buffer, 0, buffer.Length);
  stream2.Close();

  //----------------------------------
  string familyName = "";

  // TEMP ファイルを読み込む
  GlyphTypeface typeface = new GlyphTypeface(new Uri(TEMP));

  // 複数のフォントを含む場合があるので、foreach でまわす
  foreach (KeyValuePair<System.Globalization.CultureInfo, string> pair in typeface.FamilyNames)
  {
    familyName = pair.Value;
  }

  return familyName;
}

//-------------------------------------------------------------------------------------
// ODTTF ファイルの先頭の 32 バイトを指定の GUID で XOR 演算する
private void DeobfuscateData(byte[] fontData, Guid guid)
{
  byte[] buffer = guid.ToByteArray();

  for (int j = 0; j < 2; ++j)
  {
    for (int i = 0; i < 16; ++i)
    {
      fontData[i + (j * 16)] = (byte)(fontData[i + (j * 16)] ^ buffer[15 - i]);
    }
  }
}

//-------------------------------------------------------------------------------------
// 上記のメソッドを呼び出す手順
private void button1_Click(object sender, RoutedEventArgs e)
{
  string fileName = @"0b1f4fa6-4253-485c-b5fa-ca22f189690f.ODTTF";

  string familyName = this.GetFontFamilyNameFromODTTF(fileName);

  Title = familyName;
}

ところで、WEB サイトをうろついていると、ODTTF ファイルからフォント名を取得したいという要望は多いのですが、的確な答えを書いているサイトはありませんでした。回答者も分からないのか、分かっているが答えたくないのかは分かりませんが不思議な気がします。それよりも、.Net Framework の中にそのための機能を入れるべきだと思います。Microsoft はテクニックを出し惜しみする傾向がありますが、これもそれに相当するのではないでしょうか。

Note XPS ドキュメント内の Glyphs オブジェクトのフォント名を直接取得する方法が分かりました。Software の XpsAnalyser をテストしてみてください。

Glyphs クラスの Indices プロパティ

XPS ファイルの秘密」で紹介したとおり、XPS ドキュメント内のテキストをあらわすデータは .fpage ファイルの Glyphs タグにあります。この中に、Indices プロパティがありますが、これはなかなかのヤッカイものです。WPF SDK ではほとんど解説がありません。仕様書としての XPS の 5.1.3 Indices Attribute に説明はあるのですが、親切とはいえないものです。ともあれ、Indices 属性の構文を以下に示します。

    [GlyphIndex][,[Advance][,[uOffset][,[vOffset]]]]]
  GlyphIndices   = *1GlyphMapping *( ";" *1GlyphMapping )
      GlyphMapping   = *1([ClusterMapping] GlyphIndex) [GlyphMetrics]
      ClusterMapping = "(" ClusterCodeUnitCount [":" ClusterGlyphCount] ")"
      ClusterCodeUnitCount = 1*DIGIT
      ClusterGlyphCount    = 1*DIGIT
      GlyphIndex     = *DIGIT
      GlyphMetrics   = "," *1AdvanceWidth ["," *1uOffset ["," vOffset]]
  AdvanceWidth   = ["+"] RealNum
  uOffset        = ["+" | "-"] RealNum
  vOffset        = ["+" | "-"] RealNum
      RealNum        = ((1*DIGIT ["." 1*DIGIT]) | ("." 1*DIGIT)) [Exponent]
      Exponent       = *1( ("E"|"e") ("+"|"-") 1*DIGIT )

まず、グリフのインデックスとは何かについては説明します。

WPF クラスライブラリリファレンスの GlyphTypeface クラスの CharacterToGlyphMap プロパティを見れば分かりますが、Unicode の文字コードに対応する実際のフォントデータのインデックスです。たとえば、日本語の「あ」の Unicode 文字コードは 0x3042 ですが、これに対応するグリフのインデックスは 23724 (= 0x5CAC) です。フォントがメイリオの場合、「あ」に対応するグリフインデックスは次の手順で取得できます。

  GlyphTypeface typeface = new GlyphTypeface(new Uri(@"c:\windows\fonts\meiryo.ttc"));

  IDictionary<int, ushort> dic = typeface.CharacterToGlyphMap;
  ushort glyphIndex = dic[Convert.ToInt32('あ')];
  Title = glyphIndex.ToString();

上記の構文を見てのとおり、GlyphIndices は必須ではありません。これを指定しない場合、UnicodeString の対応する文字を使用します。まず、Indices プロパティの例を見てみましょう。以下のコードは WPF SDK の中にあるものを利用したものです。下図の上は、Indices プロパティがすべてセミロン ";" だけですから、UnicodeString のまま、下は別のものを設定したものです。"file" の "i" がグリフインデックス 191 になっていますが、(2:1) は操作対象の文字 "f" から 2 文字(この例では "f" と "i")を、指定のグリフインデックス(この例では 191)で指定する 1 文字で置換することを意味します。このような操作を Cluster Mapping と呼ぶようです。

ちなみに、GlyphTypeface クラスの CharacterToGlyphMap プロパティの逆の操作をする機能は提供されていないようです。通常は必要ないでしょうが、必要な場合もありますからぜひ .Net Framework の中に入れて欲しい機能ではありますね。

GlyphIndex

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  >
  <StackPanel Background="PowderBlue">
  <Glyphs
   FontUri             = "C:\WINDOWS\Fonts\TIMES.TTF"
   FontRenderingEmSize = "36"
   StyleSimulations    = "BoldSimulation"
   UnicodeString       = "Open file"
   Indices             = ";;;;;"
   Fill                = "Maroon"
   OriginX             = "50"
   OriginY             = "40"
  />
  <Glyphs
   FontUri             = "C:\WINDOWS\Fonts\TIMES.TTF"
   FontRenderingEmSize = "36"
   StyleSimulations    = "BoldSimulation"
   UnicodeString       = "Open file"
   Indices             = ";;;;;(2:1)191"
   Fill                = "Maroon"
   OriginX             = "50"
   OriginY             = "40"
  />
  </StackPanel>
</Page>

ところで、グリフインデックスの 191 を返す Times.ttf の Unicode 文字コードを力業で調べると、64257 (= 0xFB01) でした。欧米フォントにはこういうものも含むんですね。知りませんでした。

さて、Indices プロパティの構文の説明がまだ終わっていませんが、私もまだ解読に成功していません。WEB サイトをアチコチあたりましたが、情報は得られませんでした。どうも、試行錯誤で調べるほかないようです。興味のある人は、Software のページで紹介する XpsAnalyser が役に立つかもしれません。これは XPS ファイルの構造を調べるもので、Glyphs オブジェクトの内容を表示する機能があります。

−以上−