2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Cairoを使う:(その7)文字列を表示する

Last updated at Posted at 2020-09-28

英字文字列の表示

Cairoで文字列を表示するのは非常に簡単です。次のdraw()関数を試してみましょう。

#define FONT_NAME "Times New Roman"
#define STRING "Hello, world!"

void draw_text(cairo_t *c, int y)
{
  cairo_select_font_face( c, FONT_NAME, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL );
  cairo_move_to( c, 50, y+50 );
  cairo_show_text( c, STRING );
  cairo_select_font_face( c, FONT_NAME, CAIRO_FONT_SLANT_ITALIC, CAIRO_FONT_WEIGHT_NORMAL );
  cairo_move_to( c, 50, y+100 );
  cairo_show_text( c, STRING );
  cairo_select_font_face( c, FONT_NAME, CAIRO_FONT_SLANT_OBLIQUE, CAIRO_FONT_WEIGHT_NORMAL );
  cairo_move_to( c, 50, y+150 );
  cairo_show_text( c, STRING );

  cairo_select_font_face( c, FONT_NAME, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD );
  cairo_move_to( c, 370, y+50 );
  cairo_show_text( c, STRING );
  cairo_select_font_face( c, FONT_NAME, CAIRO_FONT_SLANT_ITALIC, CAIRO_FONT_WEIGHT_BOLD );
  cairo_move_to( c, 370, y+100 );
  cairo_show_text( c, STRING );
  cairo_select_font_face( c, FONT_NAME, CAIRO_FONT_SLANT_OBLIQUE, CAIRO_FONT_WEIGHT_BOLD );
  cairo_move_to( c, 370, y+150 );
  cairo_show_text( c, STRING );
}

void draw(cairo_t *c, int width, int height)
{
  /* background */
  cairo_set_source_rgb( c, 1, 1, 1 );
  cairo_rectangle( c, 0, 0, width, height );
  cairo_fill( c );
  /* text */
  cairo_set_source_rgb( c, 0, 0, 0 );
  cairo_set_font_size( c, 18 );
  draw_text( c, 0 );
  cairo_set_font_size( c, 24 );
  draw_text( c, 200 );
}

我ながらdraw_text()関数がいまいちな感じですが、見逃して下さい。出力は次のようになります。
test.png
cairo_select_font_face()でフォントと斜体にするかどうか、太字にするかどうかをいっぺんに指定します。フォントサイズだけはcairo_set_font_size()で別に与えます。
CAIRO_FONT_SLANT_ITALICとCAIRO_FONT_SLANT_OBLIQUEの違いはこの例では分かりません。Wikipediaには次のように書いてあります。

欧文斜体はイタリック体(英: Italic type)と単純に右に傾けた字体であるオブリーク体(英: Oblique type)とに分類することができる。狭義の「斜体」は「オブリーク体」を指す。日本語の場合イタリック体に相当するものはないので、ワープロソフトなどコンピュータ上での日本語文字の斜体は、文字を機械的に傾けただけのオブリーク体である。

ということで定義は異なるようで、実際いろいろなフォントで試しに表示してみたのですが、筆者の環境では今のところこれらが異なるものは見つかっていません。

自分のシステムで使えるフォントにどのようなものがあるかを知りたくなると思います。fontconfigが入っていれば、次のワンライナーで調べられます。
% fc-list | cut -d : -f 2 | cut -d , -f 1 | less

日本語文字列の表示

Cairo上では、日本語(多バイト長文字)と半角英数字の表示方法に差はありません。
FONT_NAMEとSTRINGを次のように変えてみて下さい。

#define FONT_NAME "Takao Pゴシック"
#define STRING "こんにちは!"

ただし日本語はUTF-8でエンコードされている必要があります。うまく日本語が表示されないという場合、次を試してみて下さい。
% nkf --overwrite -w8 test.c
また、フォント名も、自分のシステムで使えるものに適宜置き換えて下さい。正しく実行できれば、出力は次のようになるはずです。
test.png
表示はされましたが、これは期待と異なる結果です。先ほどのように真中と下の段が斜体にならない理由は、Takao Pゴシックフォントに斜体が登録されていないからです。多くの日本語フォントでは斜体フォントを別個に用意しておらず、それらを斜体レンダリングする際にはレギュラーフォントを変形して実行時生成する方法がとられているようです。こちらのページを拝読すると、結構奥深い問題であることが分かります。

奥深い問題はさておき、draw_text()関数を次のように変えてみます。

void draw_slant(cairo_t *c, char *str)
{
  cairo_matrix_t matrix;

  cairo_save( c );
  cairo_matrix_init( &matrix, 1, 0, -0.3, 1, 0, 0 );
  cairo_transform( c, &matrix );
  cairo_show_text( c, str );
  cairo_restore( c );
}

void draw_text(cairo_t *c, int y)
{
  cairo_select_font_face( c, FONT_NAME, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL );
  cairo_move_to( c, 50, y+50 );
  cairo_show_text( c, STRING );
  cairo_move_to( c, 50, y+100 );
  draw_slant( c, STRING );
  cairo_move_to( c, 50, y+150 );
  draw_slant( c, STRING );

  cairo_select_font_face( c, FONT_NAME, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD );
  cairo_move_to( c, 370, y+50 );
  cairo_show_text( c, STRING );
  cairo_move_to( c, 370, y+100 );
  draw_slant( c, STRING );
  cairo_move_to( c, 370, y+150 );
  draw_slant( c, STRING );
}

もはやイタリックもオブリークもないので二つ上下に並べることに深い意味はないのですが、ともかく実行すると出力は次のようになります。
test.png
曲がりなりにも斜体を出力することができました。ここで用いたcairo_matrix_init()やcairo_transform()については別の記事で説明します。

文字列のバウンディングボックス取得

文字列を表示するにあたってx,y座標を指定しますが、それに対して実際の描画はどこから始まりどこで終わるのか?文字列の幅・高さ(バウンディングボックス)は?等の情報が欲しくなったときには、cairo_text_extents()関数を使います。
次のdraw()関数を試してみましょう。

#define FONT_NAME "Times New Roman"
#define STRING "Good morning, everyone."

void draw(cairo_t *c, int width, int height)
{
  cairo_text_extents_t te;
  double x = 50, y = 100;

  /* background */
  cairo_set_source_rgb( c, 1, 1, 1 );
  cairo_rectangle( c, 0, 0, width, height );
  cairo_fill( c );
  /* text */
  cairo_select_font_face( c, FONT_NAME, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL );
  cairo_set_font_size( c, 48 );
  cairo_set_source_rgb( c, 0, 0, 0 );
  cairo_move_to( c, 50, 100 );
  cairo_text_extents( c, STRING, &te );
  cairo_show_text( c, STRING );
  /* bounding box */
  cairo_rectangle( c, x+te.x_bearing, y+te.y_bearing, te.width, te.height );
  cairo_stroke( c );
}

cairo_text_extents_t構造体はメンバにx_bearing, y_bearing, width, height, x_advance, y_advanceを持ちます。指定したx,y座標に対し実際に描画開始されるのはx_bearing, y_bearingだけオフセットした位置であり、幅はwidth、高さはheightになります。これらの情報を使い、最後のcairo_rectangle()でバウンディングボックスを出力しています。
test.png
なお、x_advance, y_advanceは、これに続けて文字列を描画した場合x, yからそれぞれどれだけオフセットした位置から開始することになるか、を表す値です。

これを利用して次のような遊びができます。

#define FONT_NAME "Takao Pゴシック"
#define STRING "こんにちは!"

void draw(cairo_t *c, int width, int height)
{
  cairo_text_extents_t te;
  cairo_pattern_t *p;

  /* background */
  cairo_set_source_rgb( c, 1, 1, 1 );
  cairo_rectangle( c, 0, 0, width, height );
  cairo_fill( c );
  /* normal text */
  cairo_select_font_face( c, FONT_NAME, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD );
  cairo_set_font_size( c, 80 );
  cairo_move_to( c, 50, 100 );
  cairo_set_source_rgb( c, 0, 0, 0 );
  cairo_show_text( c, STRING );
  /* rainbox text */
  cairo_text_extents( c, STRING, &te );
  p = cairo_pattern_create_linear( 0, 200+te.y_bearing, 0, 200+te.y_bearing+te.height );
  cairo_pattern_add_color_stop_rgb( p, 0, 1, 0, 0 );
  cairo_pattern_add_color_stop_rgb( p, 0.3, 1, 1, 0 );
  cairo_pattern_add_color_stop_rgb( p, 0.5, 0, 1, 0 );
  cairo_pattern_add_color_stop_rgb( p, 0.7, 0, 0, 1 );
  cairo_pattern_add_color_stop_rgb( p, 0.9, 0.5, 0, 1 );
  cairo_pattern_add_color_stop_rgb( p, 1, 1, 0, 1 );
  cairo_set_source( c, p );
  cairo_pattern_destroy( p );
  cairo_move_to( c, 50, 200 );
  cairo_show_text( c, STRING );
}

ある程度予想できると思いますが、出力は次のようになります。
test.png

文字列をパスに変換する

もう少し遊んでみましょう。draw()関数の末尾に次を追加します。

  /* text rim */
  cairo_move_to( c, 50, 200 );
  cairo_text_path( c, STRING );
  cairo_set_line_width( c, 3 );
  cairo_set_source_rgb( c, 0.3, 0.3, 0.1 );
  cairo_stroke( c );

出力は次のようになります。
test.png
このように、cairo_text_path()で文字列の輪郭を通常のパスに変換することができます。

英字のベースライン取得

英字を表示する際、バウンディングボックスの他にベースライン高さが知りたくなることがあるかも知れません。ややトリッキーですが、次のようにすれば分かります。

#define FONT_NAME "Times New Roman"
#define STRING "Good morning, everyone."

void draw(cairo_t *c, int width, int height)
{
  cairo_text_extents_t te;
  double x = 50, y = 100, ybase;

  /* background */
  cairo_set_source_rgb( c, 1, 1, 1 );
  cairo_rectangle( c, 0, 0, width, height );
  cairo_fill( c );
  /* font */
  cairo_select_font_face( c, FONT_NAME, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL );
  cairo_set_font_size( c, 48 );
  cairo_text_extents( c, "G", &te );
  ybase = y + te.y_bearing + te.height;
  /* bounding box */
  cairo_text_extents( c, STRING, &te );
  cairo_set_source_rgb( c, 0.5, 0.5, 0.5 );
  cairo_rectangle( c, x+te.x_bearing, y+te.y_bearing, te.width, te.height );
  cairo_stroke( c );
  /* base line */
  cairo_set_source_rgb( c, 1, 0, 0 );
  cairo_move_to( c, x+te.x_bearing, ybase );
  cairo_rel_line_to( c, te.width, 0 );
  cairo_stroke( c );
  /* text */
  cairo_set_source_rgb( c, 0, 0, 0 );
  cairo_move_to( c, 50, 100 );
  cairo_show_text( c, STRING );
}

最初のcairo_text_extents()にダミーの"G"を与えてy_bearingおよびheightを得ています。これらの和が指定表示位置に対するベースライン高さになります。出力は次のようになります。
test.png

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?