このページは過去に掲載していたものをそのまま使用しています。

EPS出力を改善する

EPS出力では日本語と英語の文字別に出力を変更する 必要があります。その仕掛けについて説明します。

データ構造の変更

テキストの中に英語と日本語が混在しているなどした時に ちゃんと表示できなかったので、epsを生成する、app/render_eps.c、 に仕掛をつくりました。

このちゃんと表示できないというのはEPSのデータ構造に問題があります。 tgifを使ってテキストで「テストtest」を書き、EPSに出力しました。

/* ascii文字と2byte文字を含む一続きの文字列 (『テストtest』の場合)*/
0 SG
/Ryumin-Light-EUC-H FF dup /WMode known {dup /WMode get 1 eq {[0 1 -1 0 0 0.3] makefont} if} if [14 0 0 -14 0 0] MS
  (\244\306\244\271\244\310) SH
0 SG
/Helvetica FF [14 0 0 -14 0 0] MS
  (test) SH

この斜字体になっているところのように、2byte文字では「/Ryumin-Light-EUC-H」を ascii文字では「/Helvetica」というように、フォントの指定を切り換える必要が あります。

このような処理を追加するためには、いくつかの方法があったのですが 2byte文字用のPSフォント名を追加する事にしました。

フォント構造体を変更する

/* 古い定義 */
typedef struct _FontData {
  char *fontname;
  char *fontname_ps;
  char *fontname_x11[NUM_X11_FONTS]; /* First choice */
} FontData;

いままでは上のような構造体を使っていたが、複数のPS用フォント名を指定できる ように改造した。

#define NUM_PS_FONTS 2
/* 新しい定義 */
typedef struct _FontData {
  char *fontname;
  char *fontname_ps[NUM_PS_FONTS];
  char *fontname_x11[NUM_X11_FONTS]; /* First choice */
} FontData;

これに合せるためにlib/font.cを書換えた。

{ "Helvetica",
  { "Helvetica", "Ryumin-Light" }, /* ← 変更点 */
  { "-adobe-helvetica-medium-r-normal-*-%d-*-*-*-*-*-*-*,-*-mincho-medium-r-normal-*-%d-*-*-*-*-*-*-*",
    "-adobe-helvetica-medium-r-normal-*-%d-*-*-*-*-*-*-*,-*-fixed-medium-r-normal-*-%d-*-*-*-*-*-*-*"
  }
},
{ "Helvetica-Bold",
  { "Helvetica-Bold", "GothicBBB-Medium" }, /* ← 変更点 */
  { "-adobe-helvetica-bold-r-normal-*-%d-*-*-*-*-*-*-*,-*-mincho-bold-r-normal-*-%d-*-*-*-*-*-*-*",
    "-adobe-helvetica-bold-r-normal-*-%d-*-*-*-*-*-*-*,-*-fixed-bold-r-normal-*-%d-*-*-*-*-*-*-*"
  }
},

このように構造を変更することによって、EPS出力時におけるフォントの指定を 複数持たせました。これを利用して実際に日本語フォントとascii文字フォントを 切り換える方法については次に述べる。

EPS出力の変更

EPS出力についての処理の全ては、app/render_eps.cの中に 書かれている。このset_font関数に次のような仕掛をいれる。

str = font_get_psfontname(font)[0];

if(strcmp(str, "Ryumin-Light")==0||strcmp(str, "GothicBBB-Medium")==0)
  /* when using the EUC-JP Charset, */
  fprintf(renderer->file, "/%s-EUC-H ff %f scf sf\n", str, (double)height);
else
  fprintf(renderer->file, "/%s-latin1 ff %f scf sf\n", str, (double)height);
}

さらに二つ目のPSフォント名に対応するset_mbfont関数を作る。 ほとんどset_font関数で一箇所だけ違う。

str = font_get_psfontname(font)[1];

if(strcmp(str, "Ryumin-Light")==0||strcmp(str, "GothicBBB-Medium")==0)
  /* when using the EUC-JP Charset, */
  fprintf(renderer->file, "/%s-EUC-H ff %f scf sf\n", str, (double)height);
else
  fprintf(renderer->file, "/%s-latin1 ff %f scf sf\n", str, (double)height);
}

これだけの機能追加で、日本語を表示するのには十分になる。あとは 適切にこの関数を呼ぶだけ。

日本語(2byte)と英語(1byte)の境界

たとえば「テストtest」という文字列があった場合に、「トt」の二つの文字の 間が境界になる。ここで文字を分割して処理を分けなければならない。 このアルゴリズムは何も考えずに、正数でなければEUCだと認識している。

以前は何も考えなさすぎて、文字の境界を次のように認識していた。

しかしこれは次のようなアルゴリズムで置き換える事ができる。

なんか頭が悪くて嫌になりますね、何やってんだか。 いづれにしても、境界なら適切なset_font関数set_mbfont関数を呼んであげます。それから、普通に draw_string関数を呼んであげましょう。

set_font関数とdraw_string関数のなぞ

この二つの関数は主にapp/render_eps.cの事を指していますが、 おおもとは、lib/render.hでプロトタイプが宣言されています。 C言語をC++風に使っているので、わかりづらいかもしれませんね。

この二つの関数は大抵一緒に使われています。それなら、面倒な処理を 全部のdraw_string関数を呼びだしている所に追加しなくても、 app/render_eps.cdraw_string関数を変更して set_font関数draw_string関数の中に隠して しまえばいいんじゃないのかなと思いました。

後になってdraw_string関数以外でもset_font関数を実行する必要のある 関数があることが発覚してバグを潜ませる結果になりました。

テキストオブジェクトの場合

lib/text.cなどでは一々テキストを出力するのに次のような 手法を取っていました。

void
text_draw(Text *text, Renderer *renderer)
{
 ...省略
 renderer->ops->set_font(renderer, text->font, text->height);
  
  pos = text->position;
  
  for (i=0;i<text->numlines;i++) {
    renderer->ops->draw_string(renderer,
                               text->line[i],
                               &pos, text->alignment,
                               &text->color);
    pos.y += text->height;
  }
 ...省略
}

日本語化のために作ったパッチは、EPSの時にフォントの設定を切り替える 必要があるためにset_mbfont()を新たに作りましたが、そういう処理は draw_string()の中に閉じ込めてしまえばよいのではないでしょうか。

なぜ、こんな事をするのかといえば、UMLのダイアグラム全部に ASCIIとEUCの境界を判断させて、フォントを切り替えさせるのが面倒だから。 lib/text.cだけでも嫌になってくる作業を、これ以外の 独自にテキスト領域を描画させている部分、全部にするなんて冗談ではない。

そういうわけで、後からコンポーネントが追加された場合の事を 考えて変更のほとんどは、renderオブジェクト側でする事にしました。 変更すると上記のプログラムは次のような形になります。

void
text_draw(Text *text, Renderer *renderer)
{
 ...省略
  pos = text->position;

  /* modified only this loop */
  for (i=0;i<text->numlines;i++) {
    renderer->ops->draw_mbstring(renderer,
				 text->line[i],
				 &pos, text->alignment,
				 &text->color,
				 text->font,
				 text->height);
    pos.y += text->height;
  }
 ...省略
}

新たにdraw_mbstring関数を作りましたが、実際はdraw_string関数 を変更しても良いわけです。ただし他の人が作った新しいコンポーネントで このプロトタイプに従って使わないとどうしようもないので、新しい関数に変更する 手間の方を選びました。

もともとdiaの内部は1byte環境を前提にしたコーディングなので、 ここまでやって日本語化(あわよくばi18n)への対応を取るための 土台ができたといった感じです。

rubyなどで使われている手法を使って2byteな文字かどうか判定する方法の 方がわかりやすいのではないかなと思う。時間が取れたらオリジナルのソースに 変更を加えてみましょう。


Yasuhiro Abe <yasu@europa.u-aizu.ac.jp>
Last modified: Tue Feb 15 14:36:21 2000