ユーニックス総合研究所

  • home
  • archives
  • windows-rs-draw-text

改行付きの文字列を描画する - Rustで作るWindowsアプリ

  • 作成日: 2023-05-09
  • 更新日: 2023-12-24
  • カテゴリ: windows-rs

改行付きの文字列を描画する

TextOutW関数は実は改行を処理できません。改行を入れても表示で改行されないので困ったもんです。
そこで登場するのがDrawTextW関数です。この関数は改行を処理して表示上でもちゃんと文字列を改行してくれます。
また指定した矩形領域の範囲で自動で折り返してくれますので使い勝手がいいです。
ここではDrawTextW関数の使い方について解説します。

WM_PAINTの編集

ウィンドウプロシージャ関数内のWM_PAINT以下を次のように編集します。

    WM_PAINT => {  
        let hdc: HDC = GetDC(window);  

        let mut rect: RECT = RECT {  
            left: 10,  
            top: 10,  
            right: 200,  
            bottom: 200,  
        };  

        // 改行付きの文字列の描画  
        let s = String::from("こんにちは。\nごきげんいかがですか?私は元気です。");  
        let mut v: Vec<u16> = s.encode_utf16().collect();  
        // v.push(0);  // ナル文字をプッシュしない  

        DrawTextW(hdc, &mut v, &mut rect, DT_LEFT | DT_WORDBREAK);  

        ReleaseDC(window, hdc);  
        LRESULT(0)  
    }  

上記の編集内容でプログラムを実行すると以下のような表示になります。

テキストの改行が処理されまたテキストが矩形の範囲で折り返されてるのがわかります。この関数の注意点としては渡すテキストはミュータブルにしないといけないことです。またテキストの描画位置と描画範囲はRECTで指定しますがこれもミュータブルにする必要があります。
C言語のDrawTextW関数では文字列の長さも指定しないといけませんがwindows-rsDrawTextW関数は内部で暗黙的に文字列の長さをAPIに渡してくれてます。
通常、APIに渡す文字列はナル文字が付加されている必要がありますが、ここでは文字ベクタにナル文字をプッシュしていません。これはDrawTextW関数が「配列の長さ」でテキストを描画するため、ナル文字が含まれているとナル文字も描画してしまうからです。

横道: PCWSTR.as_wideメソッドの動作

なぜTextOutW関数でPCWSTRas_wideメソッドを使った時はナル文字が描画されなかったのかと言うと、PCWSTRなどのas_wideメソッドは内部でC系のwcslen関数で得た文字列の長さでポインタをスライスしているからです。
PCWSTR.as_wideメソッドの挙動は以下のコードで実験できます。

use windows::{  
    core::*  
};  

fn main() {  
    let s: PCWSTR = w!("こんにちは");  

    unsafe {  
        println!("{}", s.display());  // こんにちは  
        println!("{}", s.as_wide().len());  // 5  
    }  
}  

w!マクロはナル文字付のu16の配列を生成し、そのポインタからPCWSTR構造体を生成します。つまりPCWSTRはポインタを保持するだけなのでこのw!マクロで生成されたポインタにはナル文字が含まれています。しかしas_wideメソッドを使うとそのポインタがスライスされて上記の例では長さが5の配列になりC言語の文字列の長さ、つまりwcslen関数などの返り値と一致するようになります。
つまりPCWSTRなどはナル文字を含むポインタを保持するべきで、u16の配列(ポインタではなく配列)を渡す関数などではナル文字は除去する必要がある、ということになりそうです。
この本で後述するSetWindowTextW関数などはPCWSTRを引数に指定しますので、このPCWSTRにはナル文字が含まれていないといけません。ややこしいですが注意が必要です。

DrawTextW関数

Function windows::Win32::Graphics::Gdi::DrawTextW  

pub unsafe fn DrawTextW<P0>(  
    hdc: P0,  // デバイス コンテキスト  
    lpchtext: &mut [u16],  // テキスト(文字配列)へのポインタ  
    lprc: *mut RECT,  // 整形用の四角形へのポインタ  
    format: DRAW_TEXT_FORMAT  // テキストの整形方法  
) -> i32  // 成功したらテキストの高さ、失敗した場合は`0`  
where  
    P0: IntoParam<HDC>,  

この関数は引数のテキスト(lpchtext)をデバイス コンテキストに描画します。四角形(lprc)の範囲でテキストを描画し、書式(format)に従ってテキストの表示を整えます。
またこの関数は指定の四角形の範囲にテキストをクリップしますがイタリック体などで文字が斜めになっている場合は斜めになってる部分が切り取られる可能性もあります。
指定されたフォントが四角形に対して大きすぎる場合、この関数は小さいフォントに自動で置き換えるというようなことはしません。

返り値は関数が成功した場合はテキストの高さになります。DT_VCENTERまたはDT_BOTTOMが指定されている場合、返り値は描画されたテキストの下部からのオフセットになります。

DRAW_TEXT_FORMAT構造体

Struct windows::Win32::Graphics::Gdi::DRAW_TEXT_FORMAT  

#[repr(transparent)]  
pub struct DRAW_TEXT_FORMAT(pub u32);  

この構造体はテキストの書式を指定するためのu32のラッパーでタプル構造体です。トレイトでビット演算子を実装しており複数の定数をOR演算子で指定することができます。
指定できる定数は以下になります。

  • DT_LEFT ... テキストを左揃えにする
  • DT_RIGHT ... テキストを右揃えにする
  • DT_TOP ... テキストを四角形の上部に揃える
  • DT_BOTTOM ... テキストを四角形の下部に揃える。DT_SINGLELINE 値でのみ使用される
  • DT_CENTER ... テキストを四角形の水平方向の中央に配置する
  • DT_VCENTER ... テキストを四角形の垂直方向に中央揃えにする
  • DT_WORDBREAK ... 改行するときに行を単語間で分割する
  • DT_WORD_ELLIPSIS ... 四角形に収まらない単語を切り捨て省略記号を付加する
  • DT_EXPANDTABS ... タブ文字を展開する。デフォルトの文字数は8
  • DT_SINGLELINE ... テキストを1行だけで表示する
  • DT_TABSTOP ... タブストップを設定する

これらの定数は一部です。