🦀

DirectWriteで「フォントを列挙する方法」をwindows-rsで書く

2023/06/24に公開

Microsoftのドキュメントにはフォントを列挙する方法という記事が存在し,DirectWriteを使用してフォントファミリ名を列挙する方法について説明されています.
https://learn.microsoft.com/ja-jp/windows/win32/directwrite/font-enumeration

ここで説明された方法をRustのwindows crateを使用して書いてみます.

IDWriteFactory を作成します

公式の記事ではいきなりGetSystemFontCollection()を呼び出してシステムフォントコレクションを取得するところから始めています.しかし,GetSystemFontCollection()IDWriteFactoryのメソッドなので,まずはIDWriteFactoryを作成します.

IDWriteFactoryを作成する方法については,MicrosoftのドキュメントIDWriteFactory インターフェイスの注釈に記載されており,DWriteCreateFactory()を使います.

windows crateのドキュメントからDWriteCreateFactory()を探します.ドキュメントには "Win32_Graphics_DirectWrite" が必要と記載されているので,Cargo.tomlに追加します.

Required features: "Win32_Graphics_DirectWrite"

Cargo.toml
[dependencies.windows]
version = "0.48"
features = [
    "Win32_Graphics_DirectWrite",
]

DWriteCreateFactory()の引数factorytypeは,IDWriteFactory インターフェイスの注釈の例と同じDWRITE_FACTORY_TYPE_SHAREDを指定しました.

main.rs
fn main() {
    unsafe {
        let dwrite_factory: IDWriteFactory =
            DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED).unwrap();
    }
}

手順 1: システム フォント コレクションを取得します

ここからは公式の記事と同じ手順です.GetSystemFontCollection()を使用して,フォントコレクションを取得します.

Required featuresにしたがって,Cargo.tomlに追加します.

Required features: "Win32_Foundation"

Cargo.toml
features = [
    # 追加
    "Win32_Foundation",
]

引数fontcollectionの型は,C++ではIDWriteFontCollection **であるため,NULLを代入していました.Rustでは*mut Option<IDWriteFontCollection>になっているため,Noneを代入した変数の可変参照&mut Tを渡します.

引数checkforupdatesの説明はwindows crateのドキュメントには記載されていないので,Win32 APIのドキュメントを参考にして false を指定しました.checkforupdatesの型はP0: IntoParam<BOOL>となっており,Win32のBOOLを使用しても良いのですが,より簡潔にRustのbool型を使用しました.

GetSystemFontCollection()実行後は,Option<IDWriteFontCollection>の中身を取り出します.今回はunwrap()を使用しています.

main.rs
fn main() {
    unsafe {
        // 続き
        let mut fontcollection: Option<IDWriteFontCollection> = None;
        dwrite_factory
            .GetSystemFontCollection(&mut fontcollection, false)
            .unwrap();
        let fontcollection = fontcollection.unwrap();
    }
}

手順 2: フォント ファミリ数を取得します

GetFontFamilyCount()を使用して,フォントファミリ数を取得します.

main.rs
fn main() {
    unsafe {
        // 続き
        let font_family_count = fontcollection.GetFontFamilyCount();
    }
}

For Loopを作成します

取得したフォントファミリ数でループします.残りの手順はループ内で実行します.

main.rs
fn main() {
    unsafe {
        // 続き
        for i in 0..font_family_count {
            // 残りの手順
        }
    }
}

手順 3: フォント ファミリを取得します

GetFontFamily()を使用して,フォントファミリ(IDWriteFontFamily)を取得します.

main.rs
        for i in 0..font_family_count {
            let fontfamily = fontcollection.GetFontFamily(i).unwrap();
        }

手順 4: ファミリ名を取得します

IDWriteFontFamilyGetFamilyNames()を使用して,IDWriteLocalizedStrings型のファミリ名を取得します.

main.rs
        for i in 0..font_family_count {
            // 続き
            let familynames = fontfamily.GetFamilyNames().unwrap();
        }

手順 5: ロケール名を見つけます

IDWriteLocalizedStringsFindLocaleName()を使用して,指定したロケールのファミリ名が存在するか(exists),存在するとしたら何番目か(index)を取得します.

ロケールは最初にユーザのデフォルトロケールで探した後,見つからなければen-usロケールで再度探します.それでも見つからなければ0番目を指定します.ユーザのデフォルトロケールはGetUserDefaultLocaleName()を使用して取得します.引数lplocalenameの型は&mut [u16]なので,長さがLOCALE_NAME_MAX_LENGTHu16の配列の可変参照を渡しています.私の環境ではデフォルトロケールは"ja-JP"でした.

LOCALE_NAME_MAX_LENGTHGetUserDefaultLocaleName()のRequired featuresをCargo.tomlに追加します.

Cargo.toml
features = [
    # 追加
    "Win32_System_SystemServices",
    "Win32_Globalization",
]

GetUserDefaultLocaleName()で取得したロケール([u16; _])から,FindLocaleName()の引数localenameの型P0: IntoParam<PCWSTR>を作成します.PCWSTRfrom_raw()を使うと*const u16からPCWSTRを作成できます.また,w!()マクロを使うと文字列リテラルからPCWSTRを作成できます.

引数existsの型は*mut BOOLなので,falseからBOOL型を作り,可変参照を渡します.if条件式で使うためにBOOL型をboolとして扱う場合はas_bool()を使用します.

main.rs
        for i in 0..font_family_count {
            // 続き
            let mut index = 0_u32;
            let mut exists = BOOL::from(false);

            let mut localename = [0_u16; LOCALE_NAME_MAX_LENGTH as usize];
            let default_locale_success = GetUserDefaultLocaleName(&mut localename);

            if default_locale_success > 0 {
                familynames
                    .FindLocaleName(
                        PCWSTR::from_raw(localename.as_ptr()),
                        &mut index,
                        &mut exists,
                    )
                    .unwrap();
            }

            if !exists.as_bool() {
                familynames
                    .FindLocaleName(w!("en-us"), &mut index, &mut exists)
                    .unwrap();
            }
            
            if !exists.as_bool() {
                index = 0;
            }
        }

手順 6: ファミリ名の文字列を取得します

GetStringLength()を使用して,文字列の長さを取得します.その後,長さ分の文字列バッファを用意して,GetString()を使用して文字列を取得します.

PCWSTRにはString型に変換するto_string()メソッドが用意されているので,最後にprintln!で表示するために使用します.

main.rs
        for i in 0..font_family_count {
            let length = familynames.GetStringLength(index).unwrap();
            let mut name = vec![0_u16; length as usize + 1];
            familynames.GetString(index, &mut name).unwrap();
            let name = PCWSTR::from_raw(name.as_ptr()).to_string().unwrap();
            println!("{}", name);
        }

ソースコード

Cargo.toml
[package]
name = "enum_fonts"
version = "0.1.0"
edition = "2021"

[dependencies.windows]
version = "0.48"
features = [
    "Win32_Graphics_DirectWrite",
    "Win32_Foundation",
    "Win32_System_SystemServices",
    "Win32_Globalization",
]

src/main.rs
use windows::{
    core::PCWSTR,
    w,
    Win32::{
        Foundation::BOOL,
        Globalization::GetUserDefaultLocaleName,
        Graphics::DirectWrite::{
            DWriteCreateFactory, IDWriteFactory, IDWriteFontCollection, DWRITE_FACTORY_TYPE_SHARED,
        },
        System::SystemServices::LOCALE_NAME_MAX_LENGTH,
    },
};

fn main() {
    unsafe {
        let dwrite_factory: IDWriteFactory =
            DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED).unwrap();

        let mut fontcollection: Option<IDWriteFontCollection> = None;
        dwrite_factory
            .GetSystemFontCollection(&mut fontcollection, false)
            .unwrap();
        let fontcollection = fontcollection.unwrap();

        let font_family_count = fontcollection.GetFontFamilyCount();

        for i in 0..font_family_count {
            let fontfamily = fontcollection.GetFontFamily(i).unwrap();
            let familynames = fontfamily.GetFamilyNames().unwrap();

            let mut index = 0_u32;
            let mut exists = BOOL::from(false);

            let mut localename = [0_u16; LOCALE_NAME_MAX_LENGTH as usize];
            let default_locale_success = GetUserDefaultLocaleName(&mut localename);
            if default_locale_success > 0 {
                familynames
                    .FindLocaleName(
                        PCWSTR::from_raw(localename.as_ptr()),
                        &mut index,
                        &mut exists,
                    )
                    .unwrap();
            }

            if !exists.as_bool() {
                familynames
                    .FindLocaleName(w!("en-us"), &mut index, &mut exists)
                    .unwrap();
            }

            if !exists.as_bool() {
                index = 0;
            }

            let length = familynames.GetStringLength(index).unwrap();
            let mut name = vec![0_u16; length as usize + 1];
            familynames.GetString(index, &mut name).unwrap();
            let name = PCWSTR::from_raw(name.as_ptr()).to_string().unwrap();
            println!("{}", name);
        }
    }
}

GitHubで編集を提案

Discussion