doxygen + graphviz で日本語を使った絵をドキュメントに埋め込む

観測気球

収集物の記録書庫 a data archive of collection -- collectible toys

[要旨] doxygen と graphviz を組み合わせて使うと、クラス図やコラボレーション図などの絵を組み込むことができます。ただし、モジュールの名前が日本語の場合、文字化けしてしまって、実用になりません。その解決方法がわかりましたので、解説します。
[キーワード] 文字化け,ドキュメント,コメント

« メイド喫茶に関する本 | トップページ | Visual Studio 2005 Express Edition (beta 2) »

2005.08.30

doxygen + graphviz で日本語を使った絵をドキュメントに埋め込む

doxygen と graohviz を組み合わせて使う場合の問題点

doxygen 日本語文字化け対策」により、とりあえず、doxygen が生成するドキュメントの本文(テキスト)の文字化けは解消したものの、グラフ描画ツール graphviz と組み合わせて使う場合、ラベル(モジュールの名前など)が日本語だと、文字化けしてしまうという問題が残っています。

graphviz を単独で使う場合は、以下のようにすれば日本語が使えます。

  • graphviz に渡す入力(.dot ファイル)を UTF-8 にする。
  • 日本語の TrueType フォントを使うように .dot ファイルで指定する。
  • 上記で指定したフォントのあるディレクトリ(フォルダ)を環境変数 DOTFONTPATH で指定する。

(参考: RAPを使ったRDFグラフの視覚化と日本語処理Graph 可視化ソフト graphviz)

一方、doxygen が graphviz (正確には dot) に渡す .dot ファイルは、以下のようになっています。

  • charset は latin1 (ISO-8859-1)
  • フォントは Helvetica 固定

解決方法

したがって、日本語が通るようにするには、doxygen が .dot ファイルを生成するときに

  • ラベル(モジュール名)を Shift_JIS → UTF-8 変換する
  • 指定するフォントを msminchou.ttf (MS明朝) にする(ついでにフォントサイズを 10 から 12 に変更する)
  • charset を UTF-8 にする(encoding を UnicodeUTF8 にする)

というような変更を加えればいいことになります。

実際に doxygen のソースのどこをどういじればいいかというと、dot.cpp

  • convertCode() 内に Shift_JIS → UTF-8 変換処理を入れ、
  • Helvetica を指定している(ハードコーディングしている)各所を msminchou.tff を指定するようにし、
  • writeGraphHeader() 内で QTextStream のエンコーディングを latin1 から UnicodeUTF8 に切り換えれば

OK です。 今回は、暫定的に GNU iconv (libiconv) を利用して、Shift_JIS → UTF-8 変換処理を実現しました。 本当は、translator.cpp に(GNU iconv 由来ではない)独自な sjis2utf() を書いて追加するのがいいんでしょうけど、簡単のため、かつ、いじるファイルの数を最小限におさえるため、dot.cpp のみの書き換えで対応しました。

以下、修正した内容を context diff で示します(対象となる doxygen の版数は 1.4.4 です)。

修正内容 (パッチ)

*** dot.cpp.orig	Mon Jun 13 01:06:56 2005
--- dot.cpp	Tue Aug 30 19:25:53 2005
***************
*** 34,39 ****
--- 34,112 ----
  #include <qtextstream.h>
  #include <md5.h>
  
+ //--------------------------------------------------------------------
+ /*
+  *  convert charset functions
+  *
+  *        written by H. Tsujimura (tsupo@na.rim.or.jp)  05 Feb 2004
+  */
+ 
+ #include <string.h>
+ #include "iconv.h"
+ 
+ /*! rapper for GNU libiconv
+  *  (convert charset)
+  *  \param p        target string for converting
+  *  \param inCode   charset of input string
+  *  \param outCode  charset of output string
+  *  \returns        pointer of output string
+  */
+ char    *
+ convertCode( const char *p, const char *inCode, const char *outCode )
+ {
+ #define BUFFER_SIZE 65536
+     char        inbuf[BUFFER_SIZE + 1];
+     static char outbuf[BUFFER_SIZE * 2 + 1];
+     int         result;
+     size_t      inbufSiz  = BUFFER_SIZE;
+     size_t      outbufSiz = BUFFER_SIZE * 2;
+     iconv_t     cd;
+     const char  *inp;
+     char        *outp;
+ 
+     memset( outbuf, 0x00, BUFFER_SIZE * 2 + 1 );
+     cd = iconv_open( outCode, inCode );
+     if ( cd == NULL )
+         return ( NULL );
+ 
+     strcpy( inbuf, p );
+     inp       = inbuf;
+     outp      = outbuf;
+     inbufSiz  = strlen( inp );
+     outbufSiz = BUFFER_SIZE * 2;
+     memset( outp, 0x00, outbufSiz );
+     result = iconv( cd,
+                     &inp,  &inbufSiz,
+                     &outp, &outbufSiz );
+ 
+     iconv_close( cd );
+ 
+     if ( result < 0 )
+         return ( NULL );
+ 
+     return ( outbuf );
+ #undef  BUFFER_SIZE
+ }
+ 
+ /*! convert string from Shift_JIS to UTF-8
+  *  \param      p   target string for converting (in Shift_JIS)
+  *  \returns        pointer of output string (in UTF-8)
+  */
+ char    *
+ sjis2utf( const char *p )
+ {
+     return ( convertCode( p, "CP932", "UTF-8" ) );
+ }
+ 
+ /*! convert string from EUC-JP to UTF-8
+  *  \param      p   target string for converting (in EUC-JP as japaneseEUC)
+  *  \returns        pointer of output string (in UTF-8)
+  */
+ char    *
+ euc2utf( const char *p )
+ {
+     return ( convertCode( p, "EUC-JP", "UTF-8" ) );
+ }
  
  //--------------------------------------------------------------------
  
***************
*** 68,82 ****
  
  static void writeGraphHeader(QTextStream &t)
  {
    t << "digraph G" << endl;
    t << "{" << endl;
    if (Config_getBool("DOT_TRANSPARENT"))
    {
      t << "  bgcolor=\"transparent\";" << endl;
    }
!   t << "  edge [fontname=\"Helvetica\",fontsize=10,"
!        "labelfontname=\"Helvetica\",labelfontsize=10];\n";
!   t << "  node [fontname=\"Helvetica\",fontsize=10,shape=record];\n";
  }
  
  static void writeGraphFooter(QTextStream &t)
--- 141,167 ----
  
  static void writeGraphHeader(QTextStream &t)
  {
+   static bool isJapanese  = theTranslator->idLanguage()=="japanese" || 
+                             theTranslator->idLanguage()=="japanese-en";
+ 
    t << "digraph G" << endl;
    t << "{" << endl;
    if (Config_getBool("DOT_TRANSPARENT"))
    {
      t << "  bgcolor=\"transparent\";" << endl;
    }
! 
!   if ( isJapanese ) {
!       t.setEncoding(QTextIStream::UnicodeUTF8);
!       t << "  edge [fontname=\"msmincho.ttc\",fontsize=12,"
!            "labelfontname=\"msmincho.ttc\",labelfontsize=12];\n";
!       t << "  node [fontname=\"msmincho.ttc\",fontsize=12,shape=record];\n";
!   }
!   else {
!       t << "  edge [fontname=\"Helvetica\",fontsize=10,"
!            "labelfontname=\"Helvetica\",labelfontsize=10];\n";
!       t << "  node [fontname=\"Helvetica\",fontsize=10,shape=record];\n";
!   }
  }
  
  static void writeGraphFooter(QTextStream &t)
***************
*** 560,565 ****
--- 645,670 ----
    QCString result;
    const char *p=l.data();
    char c;
+ 
+   char  *tmp = NULL;
+   static bool isJapanese  = theTranslator->idLanguage()=="japanese" || 
+                             theTranslator->idLanguage()=="japanese-en";
+ 
+   if ( isJapanese ) {
+       char  *q;
+ 
+ #if defined(_WIN32)
+       q = sjis2utf( p );
+ #else
+       q = euc2utf( p );
+ #endif
+       if ( q ) {
+           tmp = new char [strlen(q) + 1];
+           strcpy( tmp, q );
+           p = tmp;
+       }
+   }
+ 
    while ((c=*p++))
    {
      switch(c)
***************
*** 573,578 ****
--- 678,687 ----
        default:   result+=c; break;
      }
    }
+ 
+   if ( tmp )
+       delete [] tmp;
+ 
    return result;
  }
  
***************
*** 703,708 ****
--- 812,820 ----
                           bool reNumber
                          )
  {
+   static bool isJapanese  = theTranslator->idLanguage()=="japanese" || 
+                             theTranslator->idLanguage()=="japanese-en";
+ 
    t << "  Node";
    if (topDown) t << reNumberNode(cn->number(),reNumber); else t << reNumberNode(m_number,reNumber);
    t << " -> Node";
***************
*** 726,732 ****
        t << ",arrowhead=\"" << arrowStyle[ei->m_color] << "\"";
    }
  
!   if (format==BITMAP) t << ",fontname=\"Helvetica\"";
    t << "];" << endl; 
  }
  
--- 838,850 ----
        t << ",arrowhead=\"" << arrowStyle[ei->m_color] << "\"";
    }
  
!   if ( isJapanese ) {
!       t.setEncoding(QTextIStream::UnicodeUTF8);
!       if (format==BITMAP) t << ",fontname=\"msmincho.ttc\"";
!   }
!   else {
!       if (format==BITMAP) t << ",fontname=\"Helvetica\"";
!   }
    t << "];" << endl; 
  }
  
***************
*** 3074,3087 ****
  
  void DotGroupCollaboration::writeGraphHeader(QTextStream &t)
  {
    t << "digraph structs" << endl;
    t << "{" << endl;
    if (Config_getBool("DOT_TRANSPARENT"))
    {
      t << "  bgcolor=\"transparent\";" << endl;
    }
!   t << "  edge [fontname=\"Helvetica\",fontsize=8,"
!     "labelfontname=\"Helvetica\",labelfontsize=8];\n";
!   t << "  node [fontname=\"Helvetica\",fontsize=10,shape=record];\n";
    t << "rankdir=LR;\n";
  }
--- 3192,3216 ----
  
  void DotGroupCollaboration::writeGraphHeader(QTextStream &t)
  {
+   static bool isJapanese  = theTranslator->idLanguage()=="japanese" || 
+                             theTranslator->idLanguage()=="japanese-en";
+ 
    t << "digraph structs" << endl;
    t << "{" << endl;
    if (Config_getBool("DOT_TRANSPARENT"))
    {
      t << "  bgcolor=\"transparent\";" << endl;
    }
!   if ( isJapanese ) {
!       t.setEncoding(QTextIStream::UnicodeUTF8);
!       t << "  edge [fontname=\"msmincho.ttc\",fontsize=12,"
!         "labelfontname=\"msmincho.ttc\",labelfontsize=12];\n";
!       t << "  node [fontname=\"msmincho.ttc\",fontsize=12,shape=record];\n";
!   }
!   else {
!       t << "  edge [fontname=\"Helvetica\",fontsize=8,"
!         "labelfontname=\"Helvetica\",labelfontsize=8];\n";
!       t << "  node [fontname=\"Helvetica\",fontsize=10,shape=record];\n";
!   }
    t << "rankdir=LR;\n";
  }

実行例

以上の修正を施した doxygen を使って、実際に生成してみた html ファイル(のレンダリング結果)は以下のような感じになりました。

doxygen-graphvix
(クリックすると大きな画像を表示します)

ということで、これから書くプログラムのソースは、できるだけ doxygen 対応なコメントを書くことにします。


関連記事

投稿者: tsupo 2005.08.30 午後 07:44 | 固定リンク | このエントリーをはてなブックマークに追加 | このエントリを del.icio.us に登録 このエントリの del.icio.us での登録状況 | このエントリを Buzzurl に追加このエントリの Buzzurl での登録状況 | このエントリをlivedoorクリップに登録 このエントリのlivedoorクリップでの登録状況 このエントリをlivedoorクリップに登録している人の数 | 酢鶏巡回中

楽天市場


プログラミング」カテゴリ内の最近の記事

品揃え豊富で安い!NTT-X Store


アマゾンわくわく探検隊

トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/6737/5709216

この記事へのトラックバック一覧です: doxygen + graphviz で日本語を使った絵をドキュメントに埋め込む:

» doxygen 日本語文字化け対策 その3 from 観測気球
ようやく Shift_JIS 使用時に発生する doxygen の文字化けを完全に退治することができました。 続きを読む

受信: 2005.09.07 午後 08:58

コメント


正しい日本語を使うべきだ

投稿者: 酢鶏@人工無能 (2005.09.02 午前 07:20)


「doxygen 日本語文字化け対策 その3」のコメントからの続きになりますが、
調べて見た結果、吐き出されているdotファイルの中の問題の文字列が
UTF-8にきちんと変換されていないことが原因である事がわかりました。
試しにdotファイルを消さないように設定した後、doxygenを動かしてdotファイルを作成し、
dotファイルの文字列をUTF-8で書き直してDOTを通せば、ちゃんと画像が生成される事を確認しました。

そこでlibiconv側に問題があるのかと考えてMSYS上でiconvを動かしてみました。
結果は、Shift_JISからUTF-8への変換がきちんと出来ている事がわかりました。

これらの結果からlibiconvそのものが悪いと言うよりもlibiconvの呼び出し方が
悪いのではないかという結論になっています。
但し、どの部分が影響して変換がうまく行かないのかが良くわかりません。
あとはその部分が解決すればうまく通りそうなんですけれど。

投稿者: PATIO (2006.06.15 午後 01:33)


あと一歩のようですね。

今回の件とは別ですが、iconv はいわゆる機種依存文字が含まれる場合、変換に失敗しますので、機種依存文字を機種非依存な文字で代替(置換)する処理を通してから iconv に渡すようにする、といった配慮が必要になるかもしれません。

投稿者: tsupo (2006.06.17 午前 01:01)


その後、修正をいくつか加えて試していますが、
doxygenを通しただけでうまく行く状態にはなっていません。
doxygenが吐き出したdotファイルをdot.exeに直接食わせると
こちらの想定した通りに出力されますが、
なぜかdoxygen内部からdotを呼び出すとうまく行きません。
呼び出し部分を見る限りではうまく行きそうな感じなんですが、
原因が良くわからない状態です。
dotを呼び出すときに使用しているiSystemという呼び出しが
何かしているのではないかと言うところまではわかるんですが。
取り敢えずは、doxygenが吐き出したdotファイルをバッチファイルで
dot.exeに食わせて画像ファイルを作成しなおし、
しかる後にhelpworkshopに食わせるとchmができる所までは確認済みです。
doxygen一発でここまで言ってくれると楽なんですが、
今のところ、原因がつかめていません。

投稿者: PATIO (2006.07.04 午後 02:21)

コメントを書く




※イタズラ防止のため、メールアドレスを入力しないと投稿できません。

次からのコメント入力の手間を省くために、名前やメールアドレスをcookieに記憶しますか?


URL を入力すると、その URL にリンクがはられます。
なお、メールアドレスは公開されません。ご安心ください。


ワード

ニッセン

fujisan.co.jp

楽天市場