ホームページ開発ツール>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つを考えてみました。
  1. フルネームのリストをダイレクトに一括取得(Carbonビルド時のXojoネイティブと同じスタイル)
  2. まずファミリー名のリストを取得し、次いで書体名のリストを取得(Cocoaビルド時のXojoネイティブとフォントパネルの中間)
  3. まずコレクション名のリストを取得し、次いでファミリー名のリスト、書体名のリストを取得(フォントパネルと同一の手法)
 いずれの方法でも、リスト上はフォント名をローカライズ(日本語化)しておいた方が分かり易いのですが、フォント名としてそのまま指定すると認識しない場合がある?ので、別途ローカライズしない名前を、内部で保持しておくことにします。


 Xojoでの実装(一括取得)

 まずはフルネームのリストを一括取得する方法について。
  1. Xojoで新規プロジェクトを作成し、Popupメニュー(PopupMenu1)を配置
  2. Window1のプロパティに、pFont(-1)(String型)を追加
  3. 以下を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
    
  4. フォント名をセットする場合は、例えば以下のようにします。
    TextArea1.SelTextFont = pFont(PopupMenu1.ListIndex)
    

 Xojoでの実装(ファミリー名>書体)

 次は、ファミリー名のリストを取得後に書体名のリストを取得する方法について。
  1. Xojoで新規プロジェクトを作成し、Popupメニュー(PopupMenu1/PopupMenu2)を配置
  2. Window1のプロパティに、pFontF(-1)/pFontT(-1)(いずれもString型)を追加
  3. 以下を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
    
  4. 以下を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
    
    注)フォントによって、書体が日本語で表示されたり英字で表示されたりするが、フォントパネルでも同様なので、仕様のようだ。

  5. フォント名をセットする場合は、例えば以下のようにします。
    TextArea1.SelTextFont = pFontT(PopupMenu2.ListIndex)
    

 Xojoでの実装(コレクション名>ファミリー名>書体)

 最後は、コレクション名>ファミリー名の順にリストを取得後に書体名のリストを取得する方法について。
 この方式には、以下の注意点があります。
  1. Xojoで新規プロジェクトを作成し、Popupメニュー(PopupMenu1/PopupMenu2/PopupMenu3)を配置
  2. Window1のプロパティに、pFontC(-1)/pFontF(-1)/pFontT(-1)(いずれもString型)を追加
  3. 以下を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
    
  4. 以下を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
    
  5. 以下を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
    
    注)ファミリー名の重複を避ける処理では、ファミリー名がソートされていることを前提にしている。(実験の結果ではそのようになっていたので。)

  6. 以下をPopupMenu2のChangeイベントに記述
    // ファミリーが空の場合は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
    
    注1)フォントによって、書体が日本語で表示されたり英字で表示されたりするが、フォントパネルでも同様なので、仕様のようだ。
    注2)ファミリーが空の場合に異常終了しないよう、チェックするステップを先頭部分に追加した。(2014.11.02)

  7. フォント名をセットする場合は、例えば以下のようにします。
    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]