DirectWriteで「フォントを列挙する方法」をwindows-rsで書く
Microsoftのドキュメントにはフォントを列挙する方法という記事が存在し,DirectWriteを使用してフォントファミリ名を列挙する方法について説明されています.
ここで説明された方法を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"
[dependencies.windows]
version = "0.48"
features = [
"Win32_Graphics_DirectWrite",
]
DWriteCreateFactory()
の引数factorytype
は,IDWriteFactory インターフェイスの注釈の例と同じDWRITE_FACTORY_TYPE_SHARED
を指定しました.
fn main() {
unsafe {
let dwrite_factory: IDWriteFactory =
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED).unwrap();
}
}
手順 1: システム フォント コレクションを取得します
ここからは公式の記事と同じ手順です.GetSystemFontCollection()
を使用して,フォントコレクションを取得します.
Required featuresにしたがって,Cargo.toml
に追加します.
Required features: "Win32_Foundation"
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()
を使用しています.
fn main() {
unsafe {
// 続き
let mut fontcollection: Option<IDWriteFontCollection> = None;
dwrite_factory
.GetSystemFontCollection(&mut fontcollection, false)
.unwrap();
let fontcollection = fontcollection.unwrap();
}
}
手順 2: フォント ファミリ数を取得します
GetFontFamilyCount()
を使用して,フォントファミリ数を取得します.
fn main() {
unsafe {
// 続き
let font_family_count = fontcollection.GetFontFamilyCount();
}
}
For Loopを作成します
取得したフォントファミリ数でループします.残りの手順はループ内で実行します.
fn main() {
unsafe {
// 続き
for i in 0..font_family_count {
// 残りの手順
}
}
}
手順 3: フォント ファミリを取得します
GetFontFamily()
を使用して,フォントファミリ(IDWriteFontFamily
)を取得します.
for i in 0..font_family_count {
let fontfamily = fontcollection.GetFontFamily(i).unwrap();
}
手順 4: ファミリ名を取得します
IDWriteFontFamily
のGetFamilyNames()
を使用して,IDWriteLocalizedStrings
型のファミリ名を取得します.
for i in 0..font_family_count {
// 続き
let familynames = fontfamily.GetFamilyNames().unwrap();
}
手順 5: ロケール名を見つけます
IDWriteLocalizedStrings
のFindLocaleName()
を使用して,指定したロケールのファミリ名が存在するか(exists
),存在するとしたら何番目か(index
)を取得します.
ロケールは最初にユーザのデフォルトロケールで探した後,見つからなければen-us
ロケールで再度探します.それでも見つからなければ0番目を指定します.ユーザのデフォルトロケールはGetUserDefaultLocaleName()
を使用して取得します.引数lplocalename
の型は&mut [u16]
なので,長さがLOCALE_NAME_MAX_LENGTH
のu16
の配列の可変参照を渡しています.私の環境ではデフォルトロケールは"ja-JP"
でした.
LOCALE_NAME_MAX_LENGTH
とGetUserDefaultLocaleName()
のRequired featuresをCargo.toml
に追加します.
features = [
# 追加
"Win32_System_SystemServices",
"Win32_Globalization",
]
GetUserDefaultLocaleName()
で取得したロケール([u16; _]
)から,FindLocaleName()
の引数localename
の型P0: IntoParam<PCWSTR>
を作成します.PCWSTR
のfrom_raw()
を使うと*const u16
からPCWSTR
を作成できます.また,w!()
マクロを使うと文字列リテラルからPCWSTR
を作成できます.
引数exists
の型は*mut BOOL
なので,false
からBOOL
型を作り,可変参照を渡します.if条件式で使うためにBOOL
型をbool
として扱う場合はas_bool()
を使用します.
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!
で表示するために使用します.
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);
}
ソースコード
[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",
]
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);
}
}
}
Discussion