ホームページ>開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareで書体を含めたフォント名をセットする
Xojo / Real Studio Trial and Error
目次
CocoaのDeclareで書体を含めたフォント名をセットする
はじめに
以下は、Xojo Cocoaビルドについての話題です。
前回のフォント名についてのトピックは、書体名を含んだフォント名(以下、フルネーム)を埋め込んだデータが既に存在することを前提に、(データから)取得するところから始めましたが、ここで(データに)セットするケースについても纏めておきます。
なお検証には、Xojo 2014 Release 2を用いています。(Mac mini + OS X 10.9.5 Mavericks)
方針
任意のフォント名をセットする際のポイントは、どのようにして現在利用可能なフォントのフルネームを取得するか、でしょう。
(インストールされている全ての、あるいは、自分が使用したいフォントのフルネームを正確に覚えている人は別ですが…)
その点で、Xojoネイティブな手法であるFontメソッドは、ファミリー名までの取得となり、フルネームは取得できません。
なので、ここでもCocoaのAPIを利用することになる訳ですが、アプローチの手法として、以下の3つを考えてみました。
いずれの方法でも、リスト上はフォント名をローカライズ(日本語化)しておいた方が分かり易いのですが、フォント名としてそのまま指定すると認識しない場合がある?ので、別途ローカライズしない名前を、内部で保持しておくことにします。
- フルネームのリストをダイレクトに一括取得(Carbonビルド時のXojoネイティブと同じスタイル)
- まずファミリー名のリストを取得し、次いで書体名のリストを取得(Cocoaビルド時のXojoネイティブとフォントパネルの中間)
- まずコレクション名のリストを取得し、次いでファミリー名のリスト、書体名のリストを取得(フォントパネルと同一の手法)
Xojoでの実装(一括取得)
まずはフルネームのリストを一括取得する方法について。
- Xojoで新規プロジェクトを作成し、Popupメニュー(PopupMenu1)を配置
- Window1のプロパティに、pFont(-1)(String型)を追加
- 以下をWindow1のOpenイベントに記述
// 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Dim sFM As Ptr = NSClassFromString("NSFontManager") // クラスメソッドなので、まずNSFontManagerクラスを取得 Declare Function sharedFontManager Lib "Cocoa" Selector "sharedFontManager" (receiver As Ptr) As Ptr // Return NSFontManager* sFM = sharedFontManager(sFM) Dim pnt1 As Ptr Declare Function availableFonts lib "Cocoa" selector "availableFonts" (receiver as Ptr) As Ptr // Return NSArray* pnt1 = availableFonts(sFM) Dim cnt As Integer Declare Function aCount lib "Cocoa" selector "count" (receiver as Ptr) As Integer // Return Integer cnt = aCount(pnt1) Dim str As String Declare Function objectAtIndex lib "Cocoa" selector "objectAtIndex:" (receiver as Ptr, idx as Integer) As CFStringRef // Return NSString* Dim pnt2 As Ptr Dim nF As Ptr = NSClassFromString("NSFont") // クラスメソッドなので、まずNSFontクラスを取得 Declare Function fontWithNameSize Lib "Cocoa" Selector "fontWithName:size:" (receiver As Ptr, aName as CFStringRef, aSize as Integer) As Ptr // Return NSFont* Declare Function displayName lib "Cocoa" selector "displayName" (receiver as Ptr) As CFStringRef // Return NSString* Dim i As Integer for i=0 to cnt-1 str = objectAtIndex(pnt1,i) pFont.Append str // Full Name pnt2 = fontWithNameSize(nF,str,12) PopupMenu1.AddRow displayName(pnt2) // Localized Full Name next PopupMenu1.ListIndex = 0
- フォント名をセットする場合は、例えば以下のようにします。
TextArea1.SelTextFont = pFont(PopupMenu1.ListIndex)
Xojoでの実装(ファミリー名>書体)
次は、ファミリー名のリストを取得後に書体名のリストを取得する方法について。
- Xojoで新規プロジェクトを作成し、Popupメニュー(PopupMenu1/PopupMenu2)を配置
- Window1のプロパティに、pFontF(-1)/pFontT(-1)(いずれもString型)を追加
- 以下をWindow1のOpenイベントに記述
// 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Dim sFM As Ptr = NSClassFromString("NSFontManager") // クラスメソッドなので、まずNSFontManagerクラスを取得 Declare Function sharedFontManager Lib "Cocoa" Selector "sharedFontManager" (receiver As Ptr) As Ptr // Return NSFontManager* sFM = sharedFontManager(sFM) Dim pnt As Ptr Declare Function availableFontFamilies lib "Cocoa" selector "availableFontFamilies" (receiver as Ptr) As Ptr // Return NSArray* pnt = availableFontFamilies(sFM) Dim cnt As Integer Declare Function aCount lib "Cocoa" selector "count" (receiver as Ptr) As Integer // Return Integer cnt = aCount(pnt) Dim str1 As String Declare Function ObjectAtIndex lib "Cocoa" selector "objectAtIndex:" (receiver as Ptr, idx as Integer) As CFStringRef // Return NSString* Dim str2 As String Declare Function localizedNameForFamilyFace lib "Cocoa" selector "localizedNameForFamily:face:" (receiver as Ptr, aFont as CFStringRef, aFace as CFStringRef) As CFStringRef // Return NSString* Dim i As Integer for i=0 to cnt-1 str1 = objectAtIndex(pnt,i) // Family Name pFontF.Append str1 str2 = localizedNameForFamilyFace(sFM,str1,nil) // Localized Family Name PopupMenu1.AddRow str2 next PopupMenu1.ListIndex = 0
- 以下をPopupMenu1のChangeイベントに記述
注)フォントによって、書体が日本語で表示されたり英字で表示されたりするが、フォントパネルでも同様なので、仕様のようだ。// 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Dim sFM As Ptr = NSClassFromString("NSFontManager") // クラスメソッドなので、まずNSFontManagerクラスを取得 Declare Function sharedFontManager Lib "Cocoa" Selector "sharedFontManager" (receiver As Ptr) As Ptr // Return sharedFontManager sFM = sharedFontManager(sFM) Dim pnt1 As Ptr Declare Function availableMembersOfFontFamily lib "Cocoa" selector "availableMembersOfFontFamily:" (receiver as Ptr, aName as CFStringRef) As Ptr // Return NSArray* pnt1 = availableMembersOfFontFamily(sFM,pFontF(me.ListIndex)) Dim cnt As Integer Declare Function aCount lib "Cocoa" selector "count" (receiver as Ptr) As Integer // Return Integer cnt = aCount(pnt1) Dim pnt2 As Ptr Declare Function ObjectAtIndex1 lib "Cocoa" selector "objectAtIndex:" (receiver as Ptr, idx as Integer) As Ptr // Return NSArray* Dim str1 As String Declare Function ObjectAtIndex2 lib "Cocoa" selector "objectAtIndex:" (receiver as Ptr, idx as Integer) As CFStringRef // Return NSString* Dim str2 As String Declare Function localizedNameForFamilyFace lib "Cocoa" selector "localizedNameForFamily:face:" (receiver as Ptr, aFont as CFStringRef, aFace as CFStringRef) As CFStringRef // Return NSString* ReDim pFontT(-1) PopupMenu2.DeleteAllRows Dim i As Integer for i=0 to cnt-1 pnt2 = objectAtIndex1(pnt1,i) pFontT.Append objectAtIndex2(pnt2,0) // 0 = Full Name str1 = objectAtIndex2(pnt2,1) // 1 = Typeface Name str2 = localizedNameForFamilyFace(sFM,pFontF(me.ListIndex),str1) // Localized Typeface Name PopupMenu2.AddRow str2 next PopupMenu2.ListIndex = 0
- フォント名をセットする場合は、例えば以下のようにします。
TextArea1.SelTextFont = pFontT(PopupMenu2.ListIndex)
Xojoでの実装(コレクション名>ファミリー名>書体)
最後は、コレクション名>ファミリー名の順にリストを取得後に書体名のリストを取得する方法について。
この方式には、以下の注意点があります。
- collectionNamesで取得するリストでは、フォントパネルの「すべてのフォント」が「com.apple.AllFonts」と返ってきたりするので、置き換えが必要。
- availableMembersOfFontFamily:で取得するリストでは、同一ファミリー名が複数返ってくる場合があるので、重複を避ける処理が必要。
- CTFontDescriptorCopyAttributeのみ、Carbonメソッド。Xcodeではキャストが必要だったりするが、Xojoでは不要(?)。
- Xojoで新規プロジェクトを作成し、Popupメニュー(PopupMenu1/PopupMenu2/PopupMenu3)を配置
- Window1のプロパティに、pFontC(-1)/pFontF(-1)/pFontT(-1)(いずれもString型)を追加
- 以下をWindow1のOpenイベントに記述
// 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Dim sFM As Ptr = NSClassFromString("NSFontManager") // クラスメソッドなので、まずNSFontManagerクラスを取得 Declare Function sharedFontManager Lib "Cocoa" Selector "sharedFontManager" (receiver As Ptr) As Ptr // Return NSFontManager* sFM = sharedFontManager(sFM) Dim pnt1 As Ptr Declare Function collectionNames lib "Cocoa" selector "collectionNames" (receiver as Ptr) As Ptr // Return NSArray* pnt1 = collectionNames(sFM) Dim cnt As Integer Declare Function aCount lib "Cocoa" selector "count" (receiver as Ptr) As Integer // Return Integer cnt = aCount(pnt1) Dim str1 As String Declare Function ObjectAtIndex lib "Cocoa" selector "objectAtIndex:" (receiver as Ptr, idx as Integer) As CFStringRef // Return NSString* Dim i As Integer for i=0 to cnt-1 str1 = objectAtIndex(pnt1,i) pFontC.Append str1 PopupMenu1.AddRow LocalizedCollectionName(str1) // Localized Collection Name next PopupMenu1.ListIndex = 0
- 以下をWindow1のメソッドに追加(対症療法的であり、日本語にしか対応していない。若干工夫を凝らしたものを別途こちらに纏めてみました。)
メソッド名: LocalizedCollectionName 引数: str As String 戻り値型: String select case str case "com.apple.AllFonts" return "すべてのフォント" case "com.apple.UserFonts" return "日本語" case "com.apple.Favorites" return "よく使う項目" case "com.apple.Recents" return "最近使った項目" else return str end select
- 以下をPopupMenu1のChangeイベントに記述
注)ファミリー名の重複を避ける処理では、ファミリー名がソートされていることを前提にしている。(実験の結果ではそのようになっていたので。)// 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Dim sFM As Ptr = NSClassFromString("NSFontManager") // クラスメソッドなので、まずNSFontManagerクラスを取得 Declare Function sharedFontManager Lib "Cocoa" Selector "sharedFontManager" (receiver As Ptr) As Ptr // Return sharedFontManager sFM = sharedFontManager(sFM) Dim pnt1 As Ptr Declare Function fontDescriptorsInCollection lib "Cocoa" selector "fontDescriptorsInCollection:" (receiver as Ptr, aName as CFStringRef) As Ptr // Return NSArray* pnt1 = fontDescriptorsInCollection(sFM,pFontC(me.ListIndex)) Dim cnt As Integer Declare Function aCount lib "Cocoa" selector "count" (receiver as Ptr) As Integer // Return Integer cnt = aCount(pnt1) Dim pnt2 As Ptr Declare Function ObjectAtIndex lib "Cocoa" selector "objectAtIndex:" (receiver as Ptr, idx as Integer) As Ptr // Return NSArray* Dim str1 As String Declare Function CTFontDescriptorCopyAttribute Lib "Carbon" (fdesc As Ptr, aName as CFStringRef) As CFStringRef // Return CFTypeRef Dim str2 As String Declare Function localizedNameForFamilyFace lib "Cocoa" selector "localizedNameForFamily:face:" (receiver as Ptr, aFont as CFStringRef, aFace as CFStringRef) As CFStringRef // Return NSString* ReDim pFontF(-1) PopupMenu2.DeleteAllRows Dim temp As String Dim i As Integer for i=0 to cnt-1 pnt2 = objectAtIndex(pnt1,i) str1 = CTFontDescriptorCopyAttribute(pnt2,"NSFontFamilyAttribute") if str1 <> temp then pFontF.Append str1 str2 = localizedNameForFamilyFace(sFM,str1,nil) // Localized Family Name PopupMenu2.AddRow str2 temp = str1 end if next PopupMenu2.ListIndex = 0
- 以下をPopupMenu2のChangeイベントに記述
注1)フォントによって、書体が日本語で表示されたり英字で表示されたりするが、フォントパネルでも同様なので、仕様のようだ。// ファミリーが空の場合はpopupMenu3をクリアして戻る if Ubound(pFontF) < 0 then PopupMenu3.DeleteAllRows return end if // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Dim sFM As Ptr = NSClassFromString("NSFontManager") // クラスメソッドなので、まずNSFontManagerクラスを取得 Declare Function sharedFontManager Lib "Cocoa" Selector "sharedFontManager" (receiver As Ptr) As Ptr // Return sharedFontManager sFM = sharedFontManager(sFM) Dim pnt1 As Ptr Declare Function availableMembersOfFontFamily lib "Cocoa" selector "availableMembersOfFontFamily:" (receiver as Ptr, aName as CFStringRef) As Ptr // Return NSArray* pnt1 = availableMembersOfFontFamily(sFM,pFontF(me.ListIndex)) Dim cnt As Integer Declare Function aCount lib "Cocoa" selector "count" (receiver as Ptr) As Integer // Return Integer cnt = aCount(pnt1) Dim pnt2 As Ptr Declare Function ObjectAtIndex1 lib "Cocoa" selector "objectAtIndex:" (receiver as Ptr, idx as Integer) As Ptr // Return NSArray* Dim str1 As String Declare Function ObjectAtIndex2 lib "Cocoa" selector "objectAtIndex:" (receiver as Ptr, idx as Integer) As CFStringRef // Return NSString* Dim str2 As String Declare Function localizedNameForFamilyFace lib "Cocoa" selector "localizedNameForFamily:face:" (receiver as Ptr, aFont as CFStringRef, aFace as CFStringRef) As CFStringRef // Return NSString* ReDim pFontT(-1) PopupMenu3.DeleteAllRows Dim i As Integer for i=0 to cnt-1 pnt2 = objectAtIndex1(pnt1,i) pFontT.Append objectAtIndex2(pnt2,0) // 0 = Full Name str1 = objectAtIndex2(pnt2,1) // 1 = Typeface Name str2 = localizedNameForFamilyFace(sFM,pFontF(me.ListIndex),str1) // Localized Typeface Name PopupMenu3.AddRow str2 next PopupMenu3.ListIndex = 0
注2)ファミリーが空の場合に異常終了しないよう、チェックするステップを先頭部分に追加した。(2014.11.02)
- フォント名をセットする場合は、例えば以下のようにします。
TextArea1.SelTextFont = pFontT(PopupMenu3.ListIndex)
考察
一括取得、ファミリー名>書体取得では、リストが長くなり過ぎ、(特に)日本語フォントを見つけるのが大変という難点があるため、コレクション名>ファミリー名>書体取得が望ましいかな、と思われます。
ListBoxを使って、フォントパネルもどきの外観にしてもいいかもしれません。
あと、Xojoもフォントパネルもそうですが、(例えば)ボールド書体とボールド・プロパティは連動しているようなので、この辺の対応も必要かもしれません。
最後に毎度の繰り返しになりますが、アプリを一から作るのであれば、macoslibをベースにフォントパネルを利用する等した方が遥かに楽です。
お世話になったサイト
貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)
参考サイト(1):
更新履歴
2014.11.02 サンプルコード(PopupMenu2のChangeイベント)の不具合を修正
2014.11.02 LocalizedCollectionNameメソッドの改良版へのリンクを追加。
2014.10.30 新規作成
[Home] [MacSoft] [Donation] [History] [Privacy Policy] [Affiliate Policy]