次の方法で共有


第 17 章 テキストとフォント

テキストはグラフィカル環境において一般的に表示されているものですが、実際にテキストを表示するにはさまざまな難問を解かなければなりません。使用するフォントがプロポーショナルフォントかどうかの違いや、さまざまなスタイルや大きさを考慮する必要があります。つまり、テキストといえども、グラフィカルオブジェクトのように扱う必要があるのです。ところが、テキストは、抽象的な解析幾何で操作できる線や塗りつぶし領域のようなオブジェクトではないため、コンピュータグラフィック分野においても高度でニッチな分野に分類されるでしょう。タイポグラファー(フォントデザイナー)の立場では、フォントは、歴史に裏付けられた、洗練された芸術という意味合いがあります。何らかの形でフォントをコンピュータグラフィックシステムに実装するには、伝統的なタイポグラフィの概念に向き合う必要があるでしょう。アプリケーションプログラマも同様に、タイポグラフィの概念を学習しておく必要があります。

最も重要な基本認識は、テキストは読まれるものということです。平均的なユーザーはほとんど意識することはありませんが、フォントデザイン、フォントレンダリング、ページレイアウトには微妙な違いがあり、これらは可読性に影響を与えます。さらに、テキストは、純粋にはコンテンツそのものではありません。ページに出力されるテキストのスタイルによって、コンテンツの解釈が、良い方向にも、悪い方向にも変化することがあります。受け取った結婚式への招待状が、まるで社内メモのようでは興ざめしてしまいます。あるいは、博士論文が雑誌広告のようなスタイルで編集されていたのでは、審査する側は高い評価を与えたくなくなります。

17.1 | Windows環境のフォント

Microsoftが1992年に出荷したWindows 3.1によって、Windowsアプリケーションとフォントの関係は大きく変化しました。それ以前は、Windows環境で利用できるビデオディスプレイ用のフォントのほとんどは「ビットマップフォント」でした。このフォントは「ラスタフォント」とも呼ばれ、格納時の大きさも一定ではなく、一般には他の大きさにスケーリングできないものでした。また、ポリライン(折れ線)として定義されていた「ストロークフォント」(「プロッタフォント」あるいは「ベクタフォント」と呼ばれていました)も利用できましたが、魅力に乏しく、ほとんど使われることはありませんでした。

Windows 3.1ではTrueTypeが導入され、プログラマやユーザーはテキストを柔軟に操作できるようになりました。TrueTypeは、AppleとMicrosoftが共同開発した「アウトラインフォント」テクノロジであり、多数のフォントメーカーから支持されています。アウトラインフォントは無段階にスケーリングが可能であり、特定のピクセルサイズやグリッドにスケーリングされるときに歪みを防止する「ヒント情報」も内蔵されています。

また、アウトラインフォントは、他のグラフィック環境に自然に統合されるような設計になっています。第16章では、テキストをスケーリングしたり、回転したり、あるいは傾斜させる操作を学習しました。第20章では、テキストをグラフィックパスの一部として出力し、そのパスを使って輪郭、塗りつぶし、クリッピングなどを行うプログラムを紹介します。第22章では、各種のフォント関連テクニックを解説します(私の得意技も紹介します)。

1997年にAdobeとMicrosoftはOpenTypeフォントフォーマットを発表しました。このフォーマットは、TrueTypeと、Adobeのページ記述言語であるPostScriptで使用されているType 1アウトラインフォントフォーマットを折衷してできあがっています。[コントロールパネル]の[フォント]フォルダを開くと、TrueTypeフォントファイルは「TT」アイコンで、OpenTypeフォントファイルは「O」アイコンで表示されます。

Windowsの最近のバージョンには、100種類を超えるTrueTypeとOpenTypeのフォントが含まれており、ラテン語系以外のアルファベットも数多く含まれています。

ビットマップフォントとストロークフォントは、現在でもWindows環境でサポートされていますが、Windowsフォームアプリケーション内から直接利用することはできません。Windowsフォームアプリケーションから直接利用できるのは、TrueTypeとOpenTypeフォントだけです。この制限は、実際上は好ましいといえます。というのは、Windowsフォームアプリケーションは、アクセス可能なすべてのフォントを統一的に操作できるように設計されており、ビデオディスプレイとプリンタの両方のデバイスに対して同じフォントをそのまま使用できます。

Windowsフォームは、TrueTypeとOpenTypeフォントのアンチエイリアシングをサポートし、Microsoft ClearTypeもサポートしています。ClearTypeとは1998年にMicrosoftが発表した技術であり、LCDディスプレイ上のカラードット配置を有効活用することを目指しています。フォントのアンチエイリアシングとClearTypeについては、後ほど触れることにします。

17.2 | フォント

タイポグラファーは、「タイプフェイス名(単に「フェイス名」と呼ばれることもあります)」と「ポイントサイズ(「emサイズ」と呼ばれることもあります)」によりフォントを区別しています。個々のタイプフェイスは、タイプファミリに属しています。タイプファミリは通常、Bookman、Helvetica、Garamond、Timesなどの単純な名称を持っています。通常、それぞれのファミリは、いくつかのバリエーションに分かれています。

  • 文字を構成するストローク(線の太さ)は、細いものから太いものまであります。これらのレベルは、Helvetica Ultra Light、Helvetica Thin、Helvetica Light、Helvetica Bold、Helvetica Heavy、あるいはHelvetica Blackなどのタイプフェイス名から判断できます。
  • 個々の文字の幅は、通常より狭くしたり、広くすることができます。このレベルは、Helvetica Narrow、Helvetica Condensed、あるいはHelvetica Extendedなどのタイプフェイス名から判断できます。
  • 文字は、右方向に傾斜させることができます。このレベルは、Helvetica ItalicやHelvetica Obliqueなどのタイプフェイス名から判断できます。厳密に言えば、obliqueは単純に傾斜した文字を意味し、italicは同じように、通常のまっすぐな文字と少し異なるスタイルで表示される文字を指しています。小文字のaは、フォントがoblique(a)かitalic(a)を区別するための指標としてよく使われます。

これらの3種類のバリエーションが1つのタイプフェイス名で組み合わされるようなこともあります。たとえば、Helvetica Bold Extended Obliqueなどがあります。タイプフェイス名はまた、フォントの著作権者名やフォントメーカー固有のコード番号を含んでいることもあります。

TrueTypeがWindowsに最初に導入されたとき、次のようなタイプフェイスに関連付けられた、13種類のTrueTypeファイル(拡張子.ttf)が用意されていました。

  • Courier New
  • Courier New Bold
  • Courier New Italic
  • Courier New Bold Italic
  • Times New Roman
  • Times New Roman Bold
  • Times New Roman Italic
  • Times New Roman Bold Italic
  • Arial
  • Arial Bold
  • Arial Italic
  • Arial Bold Italic
  • Symbol

Courierは、固定ピッチのフォントファミリで、その出力はタイプライタのものに似ています。今日、Courierは、使われることが少なくなってきていますが、コマンドラインウィンドウ、プログラムリスト、および16進ダンプでは、依然として使われて続けています。

Times New Romanは、Timesフォントのクローンです(著作権の関係で名称が変更されています)。元々はロンドン版『Times』のために考案されたものであり、印刷物で盛んに使用されています。このフォントは読みやすさで高い評価を受けています。Arialは、人気のあるサンセリフフォントであるHelveticaのクローンです。セリフとは、文字ストロークを終了させる小さなセリフ(三角形の飾り)です。サンセリフフォントはセリフのないフォントです。セリフ付きのフォントは「ローマンフォント」と呼ばれることがあります。Symbolは、文字ではなく、一般的に使用される記号を提供しています。

すべてではないにしても、多くのフォントファミリは標準、太字、斜体、太字の斜体などのフェイスを持っています。さらに、Windowsでは、下線や取り消し線を各フォントに適用できるようになっています。

グラフィカル環境では、ユーザーはフォントという用語をフォントファミリの意味で使う傾向があります。たとえば、「このフォントをHelveticaからVerdanaに変えましょうか」などといいます。フォントとフォントファミリは、技術的には別個のものです。さらに、ユーザーは、斜体や太字(下線や取り消し線なども)は特定のフォントに適用される属性やスタイルと考えています。このため、「この単語を斜体にしたいから、タイプファイル名をLinotype PalatinoからLinotype Palatino Italicに変えよう」というユーザーはいません。

Windowsフォームは、ユーザーにわかりやすいようにフォントを管理しています。複数のフェイス名(Arial、Arial Bold、Arial Italic、およびArial Bold Italicなど)は、1つのフォントファミリ(Arialなど)に整理されてユーザーに提示されます。フェイス名は、ストローク幅や文字幅などのレベルに応じて種類が増えますが、許される組み合わせは、太字、斜体、下線、取り消し線などのスタイルだけです。Windowsフォームでは、Arial BlackタイプフェイスはArialファミリの一員とみなされず、独立した別個のフォントファミリとみなされています。

17.3 | フォントの高さと行間隔

フォントは、タイプフェイス名と共に、垂直方向のポイント数で識別されます。従来の印刷分野では、ポイントは0.01384インチとなっています。この数字は1/72インチの近似値ということもあり、コンピュータ化された印刷分野では、ポイントは1/72インチという前提になっています。

フォントのポイントサイズは、通常、ラテンアルファベット文字の、AからZまでの小文字および大文字(すなわち発音区別符を除きます)の、アセンダの上端からディセンダの下端までの上下幅となります。たとえば、「bq」全体の上下幅がフォントの高さとなります。これは、確かにポイントサイズを考える便利な方法ですが、厳密な高さを表現しているわけではありません。

活版印刷の時代には、フォントのポイントサイズといえば、文字が刻まれた金属活字の垂直方向の大きさを意味していました。文字自体は、ポイントサイズより少し小さいのが一般的だったわけです。これは一種の制限でしたが、今日では消滅し、文字の方がポイントサイズより大きくなる場合もあります。このようなことから、フォントのポイントサイズというのは、厳密な測定上の概念ではなく、印刷上の設計概念であると考えておけばよいでしょう。特定のフォントの文字の大きさは、ポイントサイズが示す数値より大きくなることも、小さくなることもありえます。このため、フォントのポイントサイズは、フォントの文字の高さを示す概算値であり、それ以外の何ものでもありません。

一般的なポイントサイズを知っていると、何かと便利です。さまざまなフォントを操作する必要がある場面では、特に便利です。『The New York Times』のほとんどは、8ポイントで印字されています。『Newsweek』は9ポイント、本書は約9ポイント(13級)で印刷されています。Windowsの既定フォントは10ポイントです。Windowsフォームの既定フォントはだいたい8ポイントです。第16章で触れたように、ユーザーは前提となるビデオディスプレイの解像度を設定する必要があります。そして、その解像度は、これらの8ポイントと10ポイントの表示上の大きさを決定する要素となります。

既に触れたように、ポイントサイズは「emサイズ」と呼ばれることもあります。この用語は、昔、大文字のM用に使用された金属活字の正方形の大きさに由来します。今日では、ほとんどの場合、emは水平方向の大きさを表現するために使用されています。特定のフォントのem幅は、フォントの垂直方向のポイントサイズと等しくなっています。たとえば、14ポイントのフォントの場合、emダッシュとemスペースは共に14ポイント幅になります。enはemの半分ですから、14ポイントのフォントのenダッシュとenスペースは共に7ポイント幅になります。

複数行にわたって連続して表示されるテキストは、ポイントサイズより少し大きめのスペースで分離されるのが一般的です。通常、行間は、ポイントサイズのおよそ115%程度のスペースで区切られます。この行間調整は、多くの欧州言語が発音区別符を必要とするため、その分のスペースを確保するという目的で行われているといわれます。しかし、行間調整は、レイアウトの美しさや可読性の向上という意味を持っているのも事実でしょう。行間にスペースがあれば、楽に読むことができます。

推奨される行間間隔は、FontクラスのHeightプロパティとGetHeightメソッドから取得できる値です(これらのプロパティとメソッドについては、他のFontプロパティと共に後述します)。多くのフォントでは、推奨値はポイントサイズより大きくなっているのが普通ですが、GraphicsクラスのMeasureStringメソッドから返される高さより小さくなっています。既に説明したように、ビデオディスプレイ上で既定のページ変換を行っている場合を除き、Heightプロパティの使用は避けるべきです。Heightプロパティは、Graphicsオブジェクトと関係がありませんから、プリンタや既定以外のページ変換には適していません。

17.4 | 既定フォント

私たちは、第2章からFontプロパティを使用してきました。このプロパティは、Controlクラスに実装され、Formを含むすべての派生クラスに継承されています。

▼Controlのプロパティ(抜粋)

プロパティ名 アクセス状態
Font Font Get/Set

この後すぐにわかるように、FontプロパティにはさまざまなFontオブジェクトを設定することができます。設定後、DrawStringメソッドを呼び出せば、目的に合った異なるフォントを適用できます。また、Fontオブジェクトを新規に作成し、DrawStringメソッド内でそれを直接使用することもできます。Fontプロパティ値を変更した場合、次に示すControlの読み取り専用の共有プロパティを使えば、プロパティを元の値に戻すことができます。

▼Controlの共有プロパティ(抜粋)

プロパティ名 アクセス状態
DefaultFont Font Get

次のようなコードを用意するだけです。


Font = DefaultFont

あるいは、次のようなメソッドを呼び出しても、元のプロパティ値を復帰させることができます。

▼Controlのメソッド(抜粋)

Sub ResetFont()

次に、フォント関連プロパティを示します。

▼Controlのプロパティ(抜粋)

プロパティ名 アクセス状態
FontHeight Integer Get/Set

このプロパティは、Font.Heightの代わりに使用できます。書き込み可能なプロパティですが、新しい値を設定してもFontプロパティは変更されません。

17.5 | いろいろなフォント

System.Drawing名前空間には、フォントを操作するための2つの重要なクラスが定義されています。

  • FontFamilyは、「Times New Roman」のような文字列により識別されます。
  • Fontは、フォントファミリ(FontFamilyオブジェクトか、そのファミリ名を識別する文字列)、属性(斜体や太字など)、ポイントサイズの組み合わせです。

まずはFontクラスから説明します。このクラスは、次のような3種類のコンストラクタを持っています。

  • 既存のFontオブジェクトを受け取るコンストラクタ
  • フォントファミリを識別する文字列を受け取るコンストラクタ
  • FontFamilyオブジェクトを受け取るコンストラクタ

最も単純なコンストラクタは、既存フォントを受け取り、新規フォントを作成します。新規に作成されるフォントは、スタイルを除き、既存フォントと同じになります。

▼Fontのコンストラクタ(抜粋)

Font(ByVal fnt As Font, ByVal fs As FontStyle)

FontStyleは列挙体であり、機能的にはビットフラグです。

▼FontStyle列挙体

メンバ
Regular 0
Bold 1
Italic 2
Underline 4
Strikeout 8

ここでは、fntが既存フォントで、フォームのFontプロパティから取得されたとしましょう。


Dim fnt As Font = Font

このフォントからfntItalicという名称の斜体バージョンのフォントを作り出す場合には、次のようなコードを記述します。


Dim fntItalic As New Font(fnt, FontStyle.Italic)

Visual Basic .NETの論理和(OR)ビット演算子を使えば、複数の列挙体メンバを組み合わせることができます。


Dim fntBoldStrikeout As New Font(fnt, FontStyle.Bold Or FontStyle.Strikeout)

次のサンプルプログラムは、フォームのFontプロパティを受け取り、太字と斜体バージョンのフォントを作成します。標準、太字、斜体の3種類のスタイルを混在させて表示します。


BoldAndItalic.vb
'----------------------------------------------
' BoldAndItalic.vb (c) 2002 by Charles Petzold
'----------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class BoldAndItalic
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(New BoldAndItalic())
    End Sub

    Sub New()
        Text = "Bold and Italic Text"
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Const str1 As String = "This is some "
        Const str2 As String = "bold"
        Const str3 As String = " text and this is some "
        Const str4 As String = "italic"
        Const str5 As String = " text."
        Dim br As New SolidBrush(clr)
        Dim fntRegular As Font = Font
        Dim fntBold As New Font(fntRegular, FontStyle.Bold)
        Dim fntItalic As New Font(fntRegular, FontStyle.Italic)
        Dim x As Single = 0
        Dim y As Single = 0

        grfx.DrawString(str1, fntRegular, br, x, y)
        x += grfx.MeasureString(str1, fntRegular).Width

        grfx.DrawString(str2, fntBold, br, x, y)
        x += grfx.MeasureString(str2, fntBold).Width

        grfx.DrawString(str3, fntRegular, br, x, y)
        x += grfx.MeasureString(str3, fntRegular).Width

        grfx.DrawString(str4, fntItalic, br, x, y)
        x += grfx.MeasureString(str4, fntItalic).Width

        grfx.DrawString(str5, fntRegular, br, x, y)
    End Sub
End Class

DrawStringメソッドはFont引数を受け取り、特定のフォントは標準、太字、斜体のいずれかであるため、複数のスタイルを混在させるには、スタイルの数だけDrawStringメソッドを呼び出す必要があります。プログラム内では、MeasureStringメソッドを使って個々のテキストの大きさを決定し、テキストの水平間隔を調整しています。

Dd297679.fig17-1(ja-jp,MSDN.10).gif

この画面を注意深く見ると、表示されているテキスト間に余分なスペースが入り込んでいることがわかると思います。このような余分なスペースの整形方法については、StringFormatクラスを説明するときに行います。

ここでは、もう1つの別の注意点も述べておきます。対処法は後述しますが、ここで使用しているFontコンストラクタは、所属するフォントファミリが要求されたスタイルをサポートしていないときには正常に動作せず、例外をスローするため、それをキャッチして適切な措置を取るとよいでしょう。このBoldAndItalicサンプルプログラムでは、フォームの既定フォントを使用していますから、例外は発生しません。この既定フォントは、すべてのスタイルをサポートしているのです。しかし、このプログラムですべてのフォントファミリを使えるわけではありません。

17.6 | 名前によるフォント作成

次に紹介する2つのFontコンストラクタは、きわめて便利であり、しかも、使い方も簡単です。フォントファミリ名、ポイントサイズ、スタイル(オプション)でフォントを指定するだけです。

▼Fontのコンストラクタ(抜粋)

Font(ByVal strFamily As String, ByVal fSizeInPoints As Single) Font(ByVal strFamily As String, ByVal fSizeInPoints As Single,                                 ByVal fs As FontStyle)

第1の引数に指定できるフォントファミリ名は、Times New Roman、Arial、Courier New、Comic Sans MSをはじめとする多数の名前です。たとえば、次のように使用します。


Dim fnt As New Font("Times New Roman", 24)

このコードは、24ポイントのTimes New Romanフォントを作成します。

私は、このようなコードでフォントを作成することが大好きです。また同時に、皆さんも最終的には、これらのFontコンストラクタを使用していくことになるでしょう。しかし、いくつかの欠点もここで紹介しておく必要があります。

指定できる名前は、プログラムが動作しているシステムにインストールされているTrueTypeかOpenTypeのフォントである必要があります。Times New Romanフォントが利用できない場合、あるいはその綴りを間違えてしまった場合、コンストラクタは既定フォントを使用します。問題は、Times New Romanフォントが確実に利用できるかどうかです。自分自身で利用するだけのコードを書いているのであれば、問題なく利用できるといえます。また、社内用のコードを書いている場合も事情は同じです。この場合には、社内のすべてのマシンに、Times New Romanフォントがインストールされていることを事前に知っているからです。しかし、ユーザーはTrueTypeフォントをアンインストールすることができます。Times New Romanフォントをシステムから削除する人はめったにいませんが、不可能なことではありません。開発したプログラムが広範囲のプラットフォーム上での動作を想定している場合、使用するフォントファミリ名を明示的に指定することは、安全性を低下させることを意味します。将来のある時点になれば、Windowsフォームプログラムは、異なる名前を持つ、多数の新しいフォントを使用する環境で動作するようになるでしょう。おそらく、そのような環境は、フォントマッピングメカニズムを実装し、既存プログラムがそのまま動作するようにしているはずです。しかし、めったにお目にかからないようなフォントファミリ名を使用するのは得策ではありません。

Times New Roman、Arial、そしてCourier Newといった一般的な3種類のフォントファミリ名を使用することが、一番安全だといえます。いくつかの別名を使用しても大丈夫でしょう。たとえば、Times New Romanに対しては「Times」、Arialに対しては「Helvetica」などを使用できます。

ポイントサイズを明示することは、それほど問題になることはありません。既に説明したように、ユーザーは、10ポイントのフォント表示が適切であるという前提のもとに、ビデオディスプレイのプロパティを設定しています。Windowsフォームでは、8ポイントのフォントで表示すれば十分な可読性があるという前提に、フォームのFontプロパティが設定されています。他の設定情報は、すべて相対的な値を持ちます。たとえば、24ポイントのフォントは、通常のWindowsフォームのフォントと比較すると、3倍の大きさになります。

1インチは72ポイントで構成されるため、24ポイントのフォントの高さはおよそ1/3インチとなります(私はここで、「およそ」という表現を使っています。既に触れたように、ポイントサイズは印刷上の概念であり、高い精度を誇る計測値ではありません)。また、24ポイントサイズのフォントは、GraphicsオブジェクトのDpiYプロパティ値の1/3ピクセルサイズに相当するフォントであると考えてしまうとよいでしょう。

ファミリ名とポイントサイズは、フォントスタイルと組み合わせて使用することができます。次のサンプルプログラムでは、18ポイントのCourier New、Arial、およびTimes New Romanフォントを表示し、スタイルとしては、標準、太字、斜体、太字の斜体を組み合わせて指定しています。これらの18ポイントのフォントのサイズは、およそ1/4インチとなります。


FontNames.vb
'------------------------------------------
' FontNames.vb (c) 2002 by Charles Petzold
'------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class FontNames
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(new FontNames())
    End Sub

    Sub New()
        Text = "Font Names"
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Dim astrFonts() As String = {"Courier New", "Arial", _
                                     "Times New Roman"}
        Dim afs() As FontStyle = {FontStyle.Regular, FontStyle.Bold, _
                                  FontStyle.Italic, _
                                  FontStyle.Bold Or FontStyle.Italic}
        Dim br As New SolidBrush(clr)
        Dim y As Single = 0
        Dim strFont As String
        Dim fs As FontStyle

        For Each strFont In astrFonts
            For Each fs In afs
                Dim fnt As New Font(strFont, 18, fs)
                grfx.DrawString(strFont, fnt, br, 0, y)
                y += fnt.GetHeight(grfx)
            Next fs
        Next strFont
    End Sub
End Class

このクラスは、PrintableFormから派生しているため、クライアント領域をクリックするだけで各種のフォント表示をプリンタで印刷できます。しかし、DrawStringメソッドに渡される座標は、文字列の左上隅の位置を示している点に注意する必要があります。つまり、個々の文字列の座標は、直前の文字列のテキストの高さを使って調整される必要があるのです。このサンプルプログラムでは、テキストの表示後、FontのGetHeightメソッドを使って座標を調整しています。

また、このサンプルプログラムは、個々のフォントはGetHeightメソッドに対して異なる値を返してくるという前提に立っています。返される値に興味のある方は、Console.WriteLineステートメントを追加してみるとよいでしょう。Times New RomanとArialフォントはいずれも、Courier Newフォントの値より少し大きい値を返してくることがわかります。しかし、他のフォントは、まったく異なる値を返してきます。GetHeightを呼び出せば、それらの値を確認できます(予測ではなく、事実を重視してください)。実行結果を次に示します。

Dd297679.fig17-2(ja-jp,MSDN.10).gif

GetHeightメソッドをHeightプロパティに置き換えてプリンタで印刷してみてください。おそらく、行間隔がわずかに変化するはずです。そのずれた幅は、ビデオディスプレイのdpi解像度と、プリンタ用に設定されている100dpi解像度の差となっているはずです。

次のサンプルプログラムは、先のサンプルプログラムと似ていますが、Times New Romanフォントを使用し、表示ポイントサイズを6ポイントから12ポイントに次々に1/4ポイント単位で増やしています。


FontSizes.vb
'------------------------------------------
' FontSizes.vb (c) 2002 by Charles Petzold
'------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class FontSizes
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(new FontSizes())
    End Sub

    Sub New()
        Text = "Font Sizes"
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Dim strFont As String = "Times New Roman"
        Dim br As New SolidBrush(clr)
        Dim y As Single = 0
        Dim fSize As Single

        For fSize = 6 To 12 Step 0.25
            Dim fnt As New Font(strFont, fSize)
            grfx.DrawString(strFont & " in " & Str(fSize) & " points", _
                            fnt, br, 0, y)
            y += fnt.GetHeight(grfx)
        Next fSize
    End Sub
End Class

実行結果を次に示します。

Dd297679.fig17-3(ja-jp,MSDN.10).gif

この画面からはっきりわかるように、10.5ポイントから劇的に表示スタイルが変化しています。それ以前は、1ピクセル幅でストロークが変化していましたから、その幅が2ピクセルになっていることが確認できると思います(フォームを拡大すると、よりはっきりわかると思います)。このような変化は、プリンタのような高解像度デバイスでは、はっきり確認できないものです。

より大きなフォントを使って、クライアント領域にすべての情報を表示したい場合、フォームのFontプロパティをコンストラクタ内で変更するとよいでしょう。次のサンプルプログラムは、前に紹介したサンプルプログラムBoldAndItalicをオーバーライドし、24ポイントのフォントでテキスト文字列を表示します。


BoldAndItalicBigger.vb
'----------------------------------------------------
' BoldAndItalicBigger.vb (c) 2002 by Charles Petzold
'----------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class BoldAndItalicBigger
    Inherits BoldAndItalic

    Shared Shadows Sub Main()
        Application.Run(new BoldAndItalicBigger())
    End Sub

    Sub New()
        Text &= " Bigger"
        Font = New Font("Times New Roman", 24)
    End Sub
End Class

実行結果を次に示します。

Dd297679.fig17-4(ja-jp,MSDN.10).gif

このプログラムでは、明らかにテキストの間に余分なスペースが入っており、読み手に不快感を与えてしまいます。この空白を詰めるにはStringFormatオブジェクトを使う必要がありますが、その方法については後述します。

17.7 | フォント名とポイントサイズ

フォントサイズはポイント単位で指定する必要はありません。次に示す2つのFontコンストラクタは、GraphicsUnit引数を受け取るようになっています。

▼Fontのコンストラクタ(抜粋)

Font(ByVal strFamily As String, ByVal fSize As Single,      ByVal gu As GraphicsUnit) Font(ByVal strFamily As String, ByVal fSize As Single,      ByVal fs As FontStyle,ByVal gu As GraphicsUnit)

次のGraphicsUnit列挙体値を、第16章で説明したPageUnitプロパティと組み合わせて使用することができます。

▼GraphicsUnit列挙体

メンバ 意味
World 0 ワールド座標単位
Display 1 Fontコンストラクタでは使用不可
Pixel 2 ピクセル単位
Point 3 1/72インチ単位
Inch 4 インチ単位
Document 5 1/300インチ単位
Millimeter 6 ミリメートル単位

コンストラクタは次のように使用します。


New Font(strFamily, fSize)

このコードは、次のように記述することと同じです。


New Font(strFamily, fSize, GraphicsUnit.Point)

さらに、次のコンストラクタはすべて同じ意味を持っています。


New Font(strFamily, 72)
New Font(strFamily, 72,   GraphicsUnit.Point)
New Font(strFamily, 1,    GraphicsUnit.Inch)
New Font(strFamily, 25.4, GraphicsUnit.Millimeter)
New Font(strFamily, 300,  GraphicsUnit.Document)

これらのコンストラクタは、すべて72ポイントのフォントを作成します。不思議に思っている人もいるかもしれませんが、この背景にあるのは、インチはポイントでは72、ミリメートルでは25.4であるという事実だけなのです。

Fontコンストラクタが受け取る引数の中では、GraphicsUnit.PixelとGraphicsUnit. Worldが少しわかりづらいかも知れません。ビデオディスプレイの場合、既定のページ変換(つまり、すべての座標と大きさをピクセル単位で表現します)を使ってテキストを表示する場合には、次の2つのコンストラクタを使って、72ポイントのフォントを作成することができます。


New Font(strFamily, grfx.DpiY, GraphicsUnit.Pixel)
New Font(strFamily, grfx.DpiY, GraphicsUnit.World)

2番目の引数は、垂直方向の1インチあたりのピクセル数を示しています。

次に紹介するサンプルプログラムは、これまで説明した7種類のコンストラクタを使用し、すべて24ポイントのフォントを作り出しています。


TwentyFourPointScreenFonts.vb
'-----------------------------------------------------------
' TwentyFourPointScreenFonts.vb (c) 2002 by Charles Petzold
'-----------------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class TwentyFourPointScreenFonts
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(New TwentyFourPointScreenFonts())
    End Sub

    Sub New()
        Text = "Twenty-Four Point Screen Fonts"
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Dim br As New SolidBrush(clr)
        Dim cyFont As Single
        Dim y As Single = 0
        Dim fnt As Font
        Dim strFamily As String = "Times New Roman"

        fnt = New Font(strFamily, 24)
        grfx.DrawString("No GraphicsUnit, 24 points", fnt, br, 0, y)
        y += fnt.GetHeight(grfx)

        fnt = New Font(strFamily, 24, GraphicsUnit.Point)
        grfx.DrawString("GraphicsUnit.Point, 24 units", fnt, br, 0, y)
        y += fnt.GetHeight(grfx)

        cyFont = 1 / 3
        fnt = New Font(strFamily, cyFont, GraphicsUnit.Inch)
        grfx.DrawString("GraphicsUnit.Inch, " & cyFont & " units", _
                        fnt, br, 0, y)
        y += fnt.GetHeight(grfx)

        cyFont = 25.4 / 3
        fnt = New Font(strFamily, cyFont, GraphicsUnit.Millimeter)
        grfx.DrawString("GraphicsUnit.Millimeter, " & cyFont & " units", _
                        fnt, br, 0, y)
        y += fnt.GetHeight(grfx)

        fnt = New Font(strFamily, 100, GraphicsUnit.Document)
        grfx.DrawString("GraphicsUnit.Document, 100 units", _
                        fnt, br, 0, y)
        y += fnt.GetHeight(grfx)

        cyFont = grfx.DpiY / 3
        fnt = New Font(strFamily, cyFont, GraphicsUnit.Pixel)
        grfx.DrawString("GraphicsUnit.Pixel, " & cyFont & " units", _
                        fnt, br, 0, y)
        y += fnt.GetHeight(grfx)

        fnt = New Font(strFamily, cyFont, GraphicsUnit.World)
        grfx.DrawString("GraphicsUnit.World, " & cyFont & " units", _
                        fnt, br, 0, y)
    End Sub
End Class

このプログラムでは、72ポイントのフォントではなく、24ポイントのフォントを使用しています。これは、すべての情報を1つの画面に収めるためであり、それ以外の意味はありません。個々のコンストラクタ内では、2番目の引数として渡されている値は1/3インチです。

ビデオディスプレイ上では、7行のすべてのテキスト行の高さは同じになります。しかし、クライアント領域をクリックしてプリンタから印刷してみると、1つの問題が発生していることに気付くはずです。最初の5行は問題なく表示されます。使用されているコンストラクタは、プリンタ用の24ポイントのフォントを無事に作成しています。しかし、最後の2行の表示に使われているフォントは、大きすぎてしまいます。

一部の読者はまだ覚えているかもしれませんが、プリンタ用のGraphicsオブジェクトのDpiXとDpiYプロパティは、プリンタ解像度そのものを示しているのです。おそらく、300、600、720、あるいはそれ以上の解像度になっているはずです。プログラム内の最後の2つのFontコンストラクタでは、その解像度の1/3を指定しています。このため、第2の引数は、100、200、240、あるいはそれ以上の値となってしまいます。プリンタの既定のページ変換では、プリンタは100dpiデバイスであるように見せかけています。このため、フォントサイズとページ変換の相互作用により、最終的に作成されるフォントは、1、2、2.3、あるいはそれ以上のインチになってしまいます。

GraphicsUnit.PixelやGraphicsUnit.Worldを使って、プリンタの既定ページ変換用の72ポイントのフォントを作成するには、次のようにコンストラクタを呼び出す必要があります。


New Font(strFamily, 100, GraphicsUnit.Pixel)
New Font(strFamily, 100, GraphicsUnit.World)

プリンタ用に24ポイントのフォントを作成する場合には、100の1/3を指定する必要があります。次に紹介するサンプルプログラムは、先に紹介したプログラムと同じですが、7行のテキスト情報がすべて24ポイントのフォントで印刷されます。


TwentyFourPointPrinterFonts.vb
'------------------------------------------------------------
' TwentyFourPointPrinterFonts.vb (c) 2002 by Charles Petzold
'------------------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class TwentyFourPointPrinterFonts
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(New TwentyFourPointPrinterFonts())
    End Sub

    Sub New()
        Text = "Twenty-Four Point Printer Fonts"
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Dim br As New SolidBrush(clr)
        Dim cyFont As Single
        Dim y As Single = 0
        Dim fnt As Font
        Dim strFamily As String = "Times New Roman"

        fnt = New Font(strFamily, 24)
        grfx.DrawString("No GraphicsUnit, 24 points", fnt, br, 0, y)
        y += fnt.GetHeight(grfx)

        fnt = New Font(strFamily, 24, GraphicsUnit.Point)
        grfx.DrawString("GraphicsUnit.Point, 24 units", fnt, br, 0, y)
        y += fnt.GetHeight(grfx)

        cyFont = 1 / 3
        fnt = New Font(strFamily, cyFont, GraphicsUnit.Inch)
        grfx.DrawString("GraphicsUnit.Inch, " & cyFont & " units", _
                        fnt, br, 0, y)
        y += fnt.GetHeight(grfx)

        cyFont = 25.4 / 3
        fnt = New Font(strFamily, cyFont, GraphicsUnit.Millimeter)
        grfx.DrawString("GraphicsUnit.Millimeter, " & cyFont & " units", _
                        fnt, br, 0, y)
        y += fnt.GetHeight(grfx)

        fnt = New Font(strFamily, 100, GraphicsUnit.Document)
        grfx.DrawString("GraphicsUnit.Document, 100 units", _
                        fnt, br, 0, y)
        y += fnt.GetHeight(grfx)

        cyFont = 100 / 3
        fnt = New Font(strFamily, cyFont, GraphicsUnit.Pixel)
        grfx.DrawString("GraphicsUnit.Pixel, " & cyFont & " units", _
                        fnt, br, 0, y)
        y += fnt.GetHeight(grfx)

        fnt = New Font(strFamily, cyFont, GraphicsUnit.World)
        grfx.DrawString("GraphicsUnit.World, " & cyFont & " units", _
                        fnt, br, 0, y)
    End Sub
End Class

プリンタ上では、すべてのテキスト行の高さが同じになります。ビデオディスプレイ上では、最初の5行のテキストは24ポイントのフォントですが、最後の2行分はビデオディスプレイの解像度と100dpiの差に応じて少し異なるでしょう。

17.8 | 単位の競合

Fontコンストラクタ内でいろいろな単位を使ってみると、フォント単位はワールド変換とページ変換とどのような関係にあるのか、という疑問を持つはずです。FontクラスとGraphicsクラスは共に、GraphicsUnit列挙体値を使っています。Fontクラスは、いくつかのコンストラクタ内でGraphicsUnit列挙体値を使用し、GraphicsクラスのPageUnitプロパティも列挙体値のいずれかに設定されます。

これまでの説明から相互作用の意味をなんとなくわかっている人もいるでしょう。ここでは、その意味を詳細に解きほぐし、2、3の基本ルールを見つけ出します。

まず、Fontオブジェクトはデバイスに依存しない、ということを念頭に置いてください。フォントを作成するときには、ワールド変換とページ変換のどちらでも問題ではありません。Fontコンストラクタは、そのようなことには一切関知しません。プログラム内では、Graphicsオブジェクトが存在するかどうかに関係なく、Fontオブジェクトを好きなときに作成できます。

Fontオブジェクト(特定の単位による特定の大きさを持っています)とGraphicsオブジェクト(ワールド変換とそれに関係するページ変換を持ちます)間の相互作用に関与する基本的なメソッドは、次の3種類しかありません。

  • DrawString ― Font引数を受け取るGraphicsクラスのメソッド
  • MeasureString ― Font引数を受け取るGraphicsクラスのメソッド
  • GetHeight ― Graphics引数を受け取るFontクラスのメソッド

グラフィック単位と変換の関係を考えるうえで注意するメソッドは、これらの3種類だけです。他にControlPaintクラスのDrawStringDisabledメソッドと、GraphicsクラスのMeasureCharacterRangesメソッドがありますが、ほとんど使用されないと考えてよいでしょう。

これらのメソッドを使用するときには、3つのルールを守る必要があります。守るのは3つだけです。次の説明に目を通しながら、時折、TwentyFourPointScreenFontsとTwentyFourPointPrinterFontsサンプルプログラムで確認してください。

**ルール1:**ワールド変換はすべてに対して同様に作用します。

これから、いくつかのグラフィックオブジェクトを出力するとしましょう。このとき、さまざまなGraphicsUnit引数を基に作成したフォントを使用し、塗りつぶし領域と複数のテキスト行を表示するとします。また、すべての情報を2倍の大きさで表示することにします。この場合、グラフィックを出力する前に、次のようなコードを記述する必要があります。


grfx.ScaleTransform(2, 2)

ワールド変換は、すべてに同じ影響を与えます。「すべて」というのは、行、塗りつぶし領域、テキスト文字列です。これらのすべては、フォントの作成方法に関係なく、その大きさが2倍になります。ところが、MeasureStringとGetHeightから返される大きさは、元のままとなっています。これは、当然そうあるべきことです。テキストとグラフィックスの位置を指定する場合には、これらのメソッドから返されるサイズを使用します。そして、ワールド変換を変更するときには、これらのサイズを変更するべきではありません。

**ルール2:**大きさ(ポイント、インチ、あるいはミリメートル)を明示的に指定して作成したフォントを使用している場合、ページ変換はテキストの物理的な大きさに影響を与えません。

たとえば、次のように72ポイントのフォントを作成するとしましょう。


Dim fnt As New Font("Arial", 72, GraphicsUnit.Point)

そして、次のようにミリメートル単位で描画処理を行いたいと考えたとします。


grfx.PageUnit = GraphicsUnit.Millimeter
grfx.PageScale = 1

テキストの実際のサイズは、ページ変換に関係なく、元のままとなります。フォントサイズは72ポイントですから、その高さは25.4単位になります。つまり、ミリメートル単位の場合、25.4mmとなります。

しかし、ページ変換により影響を受けるものもあります。それは、DrawStringメソッドへの座標値、MeasureStringメソッドから返される大きさ、そしてGetHeightから返される高さです。これらのすべての座標と大きさは、この例ではミリメートル単位となります。この影響は問題ないでしょう。というのは、DrawStringに渡す座標の算出に一般的に使用するメソッドは、GetHeightやMeasureStringであるためです。これらのメソッドから数値を取得するときには、同じ変換が有効となっていることを確認するだけでよいのです。

TwentyFourPointScreenFontsプログラムにPageUnitとPageScaleプロパティを変更するコードを挿入すると、最初の5行分のテキストは一切影響を受けないことがわかります。しかし、最後の行はページ変換の影響を受けます。これがルール3です。

**ルール3:**GraphicsUnit.PixelあるいはGraphicsUnit.World単位で作成されたフォントの場合、その大きさはワールド座標の単位で表現されることが前提となります。

表現を変えれば、フォントサイズは、Graphicsオブジェクトの線描画と領域塗りつぶしメソッドに渡される座標のように扱われます。たとえば、次のようにフォントを作成するとしましょう。


Dim fnt As New Font("Arial", 72, GraphicsUnit.World)

既定のページ変換を使用する場合、このフォントは、ビデオディスプレイ上では72ピクセル、プリンタでは72/100インチとして扱われます。次のように、ページ変換をミリメートルに設定した場合、72というフォントサイズの高さはほぼ3インチ(72を25.4で割ると、およそ3)になります。


grfx.PageUnit = GraphicsUnit.Millimeter
grfx.PageScale = 1

GraphicsUnit.WorldとGraphicsUnit.Pixelで作成されたフォントの場合、GetHeightとMeasureStringメソッドから返される値は、ページ変換の影響を受けません。このようなフォントの実際の大きさは、ページ変換で使用されている単位で表現され、これらの2つのメソッドから返される大きさも同じページ単位です。

最後に、名称的には異なる意味を持っているように見えますが、GraphicsUnit.PixelとGraphicsUnit.Worldの2つの引数間に違いはありません。

17.9 | Fontプロパティとメソッド

Fontクラスのすべてのプロパティは読み取り専用です。Fontプロパティの完全なリストを次に示します。

▼Fontのプロパティ

プロパティ名 アクセス状態 意味
Name String Get フォントファミリ名
FontFamily FontFamily Get フォントファミリ名
Style FontStyle Get コンストラクタから受け取った値
Bold Boolean Get 太字の場合はTrue
Italic Boolean Get 斜体の場合はTrue
Underline Boolean Get 下線の場合はTrue
Strikeout Boolean Get 取り消し線の場合はTrue
Size Single Get コンストラクタから受け取った値
Unit GraphicsUnit Get コンストラクタから受け取った値
SizeInPoints Single Get Sizeから算出
Height Integer Get ビデオディスプレイ用の行間隔
GdiCharSet Byte Get GDI文字列セットID
GdiVerticalFont Boolean Get 垂直フォントの場合はTrue

SizeとUnitプロパティは、フォント作成時に使用された値、すなわちコンストラクタが受け取った値がそのまま返されます。SizeInPointsプロパティは、それらの値から算出されます。GraphicsUnit.PixelとGraphicsUnit.Worldの場合、その算出はビデオディスプレイの解像度を基に行われます。

Win32 APIコードを使用する必要がない場合、実際に使用するのは1つのメソッドだけです。実は、そのメソッドは、これまで強調してきました。そのメソッド名はGetHeightで、次のように3種類のバージョンが用意されています。

▼GetHeightのメソッド(抜粋)

Function GetHeight() As Single Function GetHeight(ByVal grfx As Graphics) As Single Function GetHeight(ByVal fDpi As Single) As Single

GetHeightから返される値は、連続するテキストの行間調整に使用する値そのものです。引数を受け取らない最初のバージョンは、既定のページ変換時のビデオディスプレイにのみ適用されます。2番目のバージョンは、最も有益なものであり、出力デバイスの解像度とページ変換を考慮しています。3番目のバージョンは、仮想的な水平/垂直解像度からdpi単位の行間間隔を返します。

Win32 APIコードを内部で呼び出せるように、FontクラスはFontオブジェクトを作成する機能を持つ3種類の共有メソッドを持っています。これらのメソッドは、FromHdc、FromHfont、FromLogFontという名称を持っています。これらのメソッドを使用しない場合、クラスのコンストラクタの1つを使用する以外に、Fontオブジェクトを作成する手段はありません。これらの5個のコンストラクタについては、既に説明しています。後で、さらに4種類のコンストラクタを紹介することになっています。第10章で触れたように、FontDialogクラスはアプリケーション内で使用できるFontオブジェクトを作成し、私たちユーザーがフォントを選択できるダイアログボックスを表示します。

次に紹介するサンプルプログラムは、フォームのFontプロパティのすべてのプロパティと、GetHeightの3つのバージョンから返される値を使っています。3番目のGetHeightメソッドでは、100dpi解像度を採用しています。


AllAboutFont.vb
'---------------------------------------------
' AllAboutFont.vb (c) 2002 by Charles Petzold
'---------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class AllAboutFont
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(New AllAboutFont())
    End Sub

    Sub New()
        Text = "All About Font"
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        grfx.DrawString( _
                "Name: " & Font.Name & vbLf & _
                "FontFamily: " & Font.FontFamily.ToString() & vbLf & _
                "FontStyle: " & Font.Style.ToString() & vbLf & _
                "Bold: " & Font.Bold & vbLf & _
                "Italic: " & Font.Italic & vbLf & _
                "Underline: " & Font.Underline & vbLf & _
                "Strikeout: " & Font.Strikeout & vbLf & _
                "Size: " & Font.Size & vbLf & _
                "GraphicsUnit: " & Font.Unit.ToString() & vbLf & _
                "SizeInPoints: " & Font.SizeInPoints & vbLf & _
                "Height: " & Font.Height & vbLf & _
                "GdiCharSet: " & Font.GdiCharSet & vbLf & _
                "GdiVerticalFont: " & Font.GdiVerticalFont & vbLf & _
                "GetHeight(): " & Font.GetHeight() & vbLf & _
                "GetHeight(grfx): " & Font.GetHeight(grfx) & vbLf & _
                "GetHeight(100 DPI): " & Font.GetHeight(100), _
                Font, New SolidBrush(clr), PointF.Empty)
    End Sub
End Class

クライアント領域をクリックし、プリンタから印刷してみてください。画面情報と印刷内容は同じになっていると思いますが、2番目のGetHeight値は異なっているはずです。プリンタ上では、この値と3番目の値が同じになっているはずです。フォームのFontプロパティを他のフォントに切り替えるだけで、いろいろなフォントのプロパティを確認することができます。実行すると、次のような情報が表示されす。

Dd297679.fig17-5(ja-jp,MSDN.10).gif

ここでは、ビデオディスプレイを[大きなサイズ]に設定しています。つまり、120dpi解像度を選択しています。15ピクセルという行間隔は、およそ0.125インチ、つまり9ポイントに相当します。[通常のサイズ]に設定している場合(つまり、96dpi解像度を選択していることと同じです)、行間隔は12になり、Heightプロパティは12という値が入っているはずです。

既に紹介したFontクラスの1番目のコンストラクタは、既存フォントから新しいフォントを作成しますが、スタイルプロパティは異なるものを使用します。同じ情報を表示する場合でも、大きさを変えたいような場面があります。

たとえば、指定した矩形領域内に入るように、フォントを作成したいこともあります。この場合、まずフォントと表示するテキストを用意します。次に、MeasureStringメソッドを使って表示文字列の大きさを求め、矩形領域の大きさに適合するサイズのフォントを新しく作成します。次に紹介するサンプルプログラムは、「Howdy, world!」(意味はHello, world!と同じで、文字構成もアセンダとディセンダを含んでいます)です。表示されるテキストは、クライアント領域の大きさに合わせて最大限にスケーリングされます。


HowdyWorld.vb
'-------------------------------------------
' HowdyWorld.vb (c) 2002 by Charles Petzold
'-------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class HowdyWorld
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(New HowdyWorld())
    End Sub

    Sub New()
        Text = "Howdy, world!"
        MinimumSize = _
                Size.op_Addition(SystemInformation.MinimumWindowSize, _
                                 New Size(0, 1))
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Dim fnt As New Font("Times New Roman", 10, FontStyle.Italic)
        Dim szf As SizeF = grfx.MeasureString(Text, fnt)
        Dim fScale As Single = Math.Min(cx / szf.Width, cy / szf.Height)

        fnt = New Font(fnt.Name, fScale * fnt.SizeInPoints, fnt.Style)
        szf = grfx.MeasureString(Text, fnt)
        grfx.DrawString(Text, fnt, New SolidBrush(clr), _
                        (cx - szf.Width) / 2, (cy - szf.Height) / 2)
    End Sub
End Class

コンストラクタ内では、MinimumSizeプロパティが設定されていますが、この設定により、クライアント領域の高さが0にならないようにしています。0になると、フォントサイズが0になり、例外が発生してしまいます。

DoPageメソッドは、Fontオブジェクトの作成から開始していますが、フォームのFontプロパティに設定されているフォントや、他のどこかで作成済みのフォントをそのまま使用することもできます。重要なことは、プログラム内では、フォントを作成するために必要とされる引数を知らなくてもよいことです。

DoPageでは、次にMeasureStringを呼び出し、先に作成したフォントを基にして、文字列の長さを決定しています(偶然、ここではフォームのTextプロパティになっています)。3番目のステートメントでは、クライアント領域(プリンタページの印字可能な領域)とテキストの大きさの関係を基に、スケーリング係数を算出しています。ここで注意してほしいのは、水平と垂直の両方向のスケーリング係数を決めるために、Math.Minメソッドが使用されていることです。

次に、DoPageでは既存フォントから新規フォントを作成していますが、fScale係数を使ってポイントサイズをスケーリングしています。この処理では、MeasureStringメソッドが再度呼び出されています。別の方法としては、直前のSizeFオブジェクトのWidthとHeightにfScale係数を掛け合わせてもよいでしょう。DoPageの最後では、文字列をクライアント領域の中央位置に表示するメソッドが呼び出されています。

Dd297679.fig17-6(ja-jp,MSDN.10).gif

ウィンドウの幅が十分大きい場合には、フォントの大きさはウィンドウの高さに応じて決まります(fScale係数値が高さにより変化するため)。

Dd297679.fig17-7(ja-jp,MSDN.10).gif

矩形領域の縦横比に関係なく、その領域に適合するフォントを作成することが可能でしょうか。文字がゆがんでしまうことは当然ですが、可能です。このようなことは、Fontのコンストラクタではできません。Fontコンストラクタ内では、フォントの高さを指定します。文字幅はその高さに応じて決められます。

しかし、ワールド変換を使用すれば、テキスト文字の縦横比を調整することができます。次に紹介するHowdyWorldFullFitは、このテクニックを応用しています。その他の機能はHowdyWorldと同じです。


HowdyWorldFullFit.vb
'--------------------------------------------------
' HowdyWorldFullFit.vb (c) 2002 by Charles Petzold
'--------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class HowdyWorldFullFit
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(New HowdyWorldFullFit())
    End Sub

    Sub New()
        Text = "Howdy, world!"
        MinimumSize = _
                Size.op_Addition(SystemInformation.MinimumWindowSize, _
                                 New Size(0, 1))
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Dim fnt As New Font("Times New Roman", 10, FontStyle.Italic)
        Dim szf As SizeF = grfx.MeasureString(Text, fnt)
        Dim fScaleHorz As Single = cx / szf.Width
        Dim fScaleVert As Single = cy / szf.Height

        grfx.ScaleTransform(fScaleHorz, fScaleVert)
        grfx.DrawString(Text, fnt, New SolidBrush(clr), 0, 0)
    End Sub
End Class

このサンプルプログラムは、水平方向と垂直方向の大きさを決めるためのスケーリング係数を個別に算出しています。算出されたスケーリング係数は、ScaleTransformメソッドに直接渡しています。DrawString呼び出しは、テキストを座標(0, 0)に表示します。

Dd297679.fig17-8(ja-jp,MSDN.10).gif

いつものように、プリンタに打ち出せば、文字列がほぼ用紙の高さまで拡大されて印字されているはずです。

この画面(とHowdyWorldプログラムの2番目の画面)で注意してほしいのは、テキストはクライアント領域の高さ一杯にまで拡大されないことです。アセンダの上に余分な空白があります。この空白は発音区別符を挿入するために使用されるのが普通です。たとえば、テキスト文字列がA、AあるいはA(Unicode文字の&H00C0、&H00C1、&H00C2)を含んでいる場合、アクセント記号はクライアント領域の上端に表示されます。

Windowsフォームライブラリでは、アプリケーションで発音区別符に割り当てるスペースを決定することはできません。また、フォントのx高(xのように、アセンダのない文字のベースラインからの高さ)を指定することもできません。これらの値はWindowsフォームライブラリに対して公開されていないためです。しかし、ほかに公開されているフォントメトリクスは多くあり、それらについては本章で解説します。また、パスを使用すれば、おおよそのx高を指定することは可能です。パスは第20章で解説します。

17.10 | FontFamilyによる新規フォントの作成

ここではさらに、4種類のFontコンストラクタを紹介しますが、基本的にはこれまで説明してきたコンストラクタと同じです。しかし、受け取る第1の引数がファミリ名という文字列ではなく、FontFamilyというオブジェクトである点がこれまでのコンストラクタと異なります。

▼Fontのコンストラクタ(抜粋)

Font(ByVal ff As FontFamily, ByVal fSizeInPoints As Single) Font(ByVal ff As FontFamily, ByVal fSizeInPoints As Single,      ByVal fs As FontStyle) Font(ByVal ff As FontFamily, ByVal fSize As Single,      ByVal gu As GraphicsUnit) Font(ByVal ff As FontFamily, ByVal fSize As Single,      ByVal fs As FontStyle, ByVal gu As GraphicsUnit)

それではいったい、FontFamilyオブジェクトをどのように取得できるのでしょうか。

1つの方法としては、既存フォントから取得できます。たとえば、既存フォントから新規フォントを作成し、そのサイズを変更したいとしましょう。この場合、Howdy Worldサンプルプログラムで行ったように、既存フォントのNameプロパティを次のように参照します。


Dim fnt18 As New Font(fnt.Name, 18, fnt.Style)

あるいは、FontFamilyプロパティを第1の引数として使用することもできます。


Dim fnt18 As New Font(fnt.FontFamily, 18, fnt.Style)

FontFamilyオブジェクトを取得するもう1つの方法は、3つのFontFamilyコンストラクタのうちの1つを使用することです。

▼FontFamilyのコンストラクタ

FontFamily(ByVal strFamily As string ) FontFamily(ByVal gff As GenericFontFamilies) FontFamily(ByVal strFamily As String,            ByVal fontcoll As FontCollection)

1番目のコンストラクタは、FontFamilyオブジェクトはフォントファミリ名により定義されていることを強く暗示しています。実際、次のようなコードをよく見かけます。


Dim fnt As New Font(strFamily, fSizeInPoints)

このコードは、次のようなコードを単純化したものです。


Dim fnt As New Font(New FontFamily(strFamily), fSizeInPoints)

FontFamilyの唯一の非共有プロパティは、Nameです。

▼FontFamilyの非共有プロパティ

プロパティ名 アクセス状態 意味
Name String Get フォントファミリ名

これまでは、FontFamilyを明示的に作成することなくFontを作成してきましたが、ときにはまずFontFamilyを取得し、それを独自に用意した変数に格納しておくと便利なこともあります。


Dim ff As New FontFamily(strFamily)

作成後、IsStyleAvailableメソッドを使用すれば、特定のスタイルが利用できるかどうかを判断できます。

▼IsStyleAvailableのメソッド(抜粋)

Function IsStyleAvailable(ByVal fs As FontStyle) As Boolean

すべてのTrueTypeとOpenTypeフォントが、太字と斜体バージョンを持っているわけではありません。サポートされていないスタイルを指定して斜体や太字フォントを作成しようとすると、例外が発生します。さらに悪いことに、すべてのフォントが標準バージョンを持っているわけではありません。このため、次のようなコードを用意すれば、より安全といえるでしょう。


If (ff.IsStyleAvailable(FontStyle.Italic))Then
    fntItalic = New Font(ff, 24, FontStyle.Italic)
ElseIf (ff.IsStyleAvailable(FontStyle.Regular)Then
    fntItalic = New Font(ff, 24, FontStyle.Regular)
Else
    fntItalic = New Font(ff, 24, FontStyle.Bold)
End If

このコードでは、結果的に斜体フォントを作成できないことになるかもしれませんが、少なくとも例外の発生は防止できます。

本章の冒頭で紹介したように、多くのフォントファミリでは、斜体、太字、太字の斜体などの各スタイルをサポートするために、個別のファイルを用意しています。いくつかの場面では、Windowsは斜体と太字を合成します。これは、通常の文字を脚色することにより、斜体、太字、太字の斜体を作り出していることを意味します。シンボルフォントであるWingdingsとWebdingsは、まさにこの例なのです。

FontFamilyの2番目のコンストラクタは、System.Drawing.Text名前空間で定義されているGenericFontFamilies列挙体値を要求します。

▼GenericFontFamilies列挙体

メンバ 意味
Serif 0 例:Times New Roman
SansSerif 1 例:Arial
Monospace 2 例:Courier New

既に述べたように、フォントファミリ名からフォントを作成する場合には、問題が発生してしまう可能性があります。システムによっては、指定したフォントファミリ名が存在しないこともあるからです。


fnt = New Font("Times New Roman", 24)

このような固有名に頼ることなく、次のようなコンストラクタを使用すれば、問題の発生する可能性は低くなるでしょう。


fnt = New Font(New FontFamily(GenericFontFamilies.Serif), 24)

これは、一見すると入力の負担が増え、優雅ではない印象を与えます。しかし、同じことをより効率的に記述できる方法が用意されているのです。この方法では、FontFamilyの共有プロパティの1つを使用します。

▼FontFamilyの共有プロパティ(抜粋)

プロパティ名 アクセス状態 意味
GenericSerif FontFamily Get 例:Times New Roman
GenericSansSerif FontFamily Get 例:Arial
GenericMonospace FontFamily Get 例:Courier New

これらのプロパティにより、次のようなコードを記述できます。


fnt = New Font(FontFamily.GenericSerif, 24)

FontFamilyの3番目のコンストラクタについては、後述します。

17.11 | フォントデザインメトリック入門

Windowsをはじめとする各種グラフィック環境でフォントを使った経験を持つプログラマの多くは、おそらく、Windowsフォームはフォントの大きさに関する情報を十分提供してくれないと感じることでしょう。これまでのところでは、フォントの高さを知りたい場合、ポイントサイズ(何度も強調しているように、これは印刷上の設計概念であり、フォント文字サイズの大まかな値を示しているにすぎません)、MeasureStringから取得するフォント文字の垂直方向の最大値、および推奨の行間隔(これはGetHeightから返される値であり、ビデオディスプレイのHeightにのみ適用される整数)という3種類の情報しか提供されていません。

この他にもう1つフォント情報が提供されていますが、この情報はベースラインの位置情報だと思われます(この微妙な表現の意味は、後ではっきりします)。フォントのベースラインとは、アセンダとディセンダの境界線であり、アセンダはこの境界線より上に、ディセンダはこの境界線より下に向かって、それぞれ表示されます。このため、(DrawStringメソッドに渡す)文字の最上位と最下位からの相対位置がわかれば、同一行に各種のフォントで文字を表現できることになります。表現を変えれば、この相対位置がわからなければ、複数のフォントを組み合わせることはできません。

この情報は、FontFamilyクラスから取得できます。はっきりしたことは言えないのですが、いくつかの極東地域と中東地域で使用されているフォントでは有効に機能しません。しかし、利用できる情報の中では、最も貴重な情報といってよいでしょう。

FontFamilyクラスは、フォントに関するより多くのサイズ情報を取得するための4つのメソッドを提供しています。これらのメソッドは、すべてFontStyle列挙体値を引数として受け取ります。

▼FontFamilyのメソッド(抜粋)

Function GetEmHeight(ByVal fs As FontStyle) As Integer Function GetCellAscent(ByVal fs As FontStyle) As Integer Function GetCellDescent(ByVal fs As FontStyle) As Integer Function GetLineSpacing(ByVal fs As FontStyle) As Integer

これらのメソッドが返す値は、「デザインメトリック」と呼ばれています。元々は、フォントをデザインした人が設定した値(少なくともTrueTypeバージョンの値)という意味を持っています。これらのデザインメトリック値は、フォントファミリから作成されるフォントの最終的な大きさとは独立しています。

それでは例を1つ見てみましょう。Times New RomanからFontFamilyを作成し、FontStyle.Regular(あるいは他の任意のFontStyle値)を使ってこれらの4つのメソッドを呼び出すと、次のような数値が返されます。

▼Times New Romanデザインメトリック

メトリック
em四角形の高さ 2048
アセント 1825
ディセント 443
アセント+ディセント 2268
行間隔 2355

em四角形の高さ(Em Height)は、フォントデザイナが使用したグリッドであり、各行の座標とフォント内の各文字を定義する曲線座標を指定します。この値は、一般的には2048となっていますが、まれに1000や256となっていることもあります。

アセント(Ascent)はベースラインから上にあるフォント文字の高さ(読分け文字も含みます)であり、ディセント(Descent)はベースラインより下にある文字の高さです。Times New Romanの場合、アセントとディセントの合計は、フォント文字の実際の高さになります(表参照)。

行間隔(Line spacing)値は、3つの部分に分かれます。3つの部分とは、ベースラインの上にあるアセント、ベースラインの下にあるディセント、ディセントの下にあるスペースです。このような関係を図にすれば、次のようになります。

Dd297679.fig17-130(ja-jp,MSDN.10).gif

いくつかのフォントでは、行間隔値がアセントとディセントの合計より大きくなっています。また、スペースが0で、アセントとディセントの合計値が行間隔値と同じになっているフォントもあります。

em四角形の高さの値については、真剣に考えないでください。この値は、他のデザインメトリック値を理解するうえでのリファレンス的な情報にすぎません。

それでは、72ポイントのTimes New Romanフォントを作成する例を見てみましょう。この例では、PageUnitプロパティをGraphicsUnit.Pointに設定します。これにより、グラフィックデバイスの解像度に関係なく、GetHeightから返される値はポイント単位になります。次のような結果が返されるはずです(ただし、小数点2桁で丸めています)。

▼Times New Romanフォントメトリック

プロパティ/メソッド 値(ポイント単位)
fnt.SizeInPoints 72
fnt.GetHeight(grfx) 82.79

この表と先に紹介したデザインメトリックの表の間には、整合性があるのでしょうか。もちろんあります。GetHeightから取得できる値は、それらのデザインメトリックから分岐しているのです。em四角形の高さと呼ばれるデザインメトリックは、フォントのポイントサイズに相当します。72を2048で割ると、0.03515625となります。この比は、フォント文字の座標をポイントサイズに変換するためのスケーリング係数なのです。スケーリング係数に行間隔メトリック(2355)を掛け合わせると、82.79となり、GetHeightから返される値そのものです。

このようなことから、同じ係数をアセントとディセントデザインメトリックに適用すれば、以前には取得できなかった各種の値が得られます。

▼Times New Romanメトリック

メトリック デザイン
メトリック値
72ポイント
フォントの値
(ポイント単位)
プロパティ/メソッド
Em Height 2048 72 fnt.SizeInPoints
Ascent 1825 64.16  
Descent 443 15.57  
Ascent + Descent 2268 79.73  
Line Spacing 2355 82.79 fnt.GetHeight(grfx)

デザインメトリックから分岐した情報を使用すれば、テキストをベースライン上に描画することができます。ここで1つの例を考えてみましょう。この例では、ベースラインをクライアント領域の中心を通る水平線とし、その線上に144ポイントのTimes New Romanフォントでテキストを描画します。サンプルコードは次のようになるでしょう。


TextOnBaseline.vb
'-----------------------------------------------
' TextOnBaseline.vb (c) 2002 by Charles Petzold
'-----------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class TextOnBaseline
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(New TextOnBaseline())
    End Sub

    Sub New()
        Text = "Text on Baseline"
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Dim yBaseline As Single = cy \ 2
        Dim pn As New Pen(clr)

        ' クライアント領域の中心を通るようにベースラインを描画する
        grfx.DrawLine(pn, 0, yBaseline, cx, yBaseline)

        ' 144ポイントのフォントを作成する
        Dim fnt As New Font("Times New Roman", 144)

        ' メトリック値を取得し計算する
        Dim cyLineSpace As Single = fnt.GetHeight(grfx)
        Dim iCellSpace As Integer = fnt.FontFamily.GetLineSpacing(fnt.Style)
        Dim iCellAscent As Integer = fnt.FontFamily.GetCellAscent(fnt.Style)
        Dim cyAscent As Single = cyLineSpace * iCellAscent / iCellSpace

        ' ベースライン上にテキストを表示する
        grfx.DrawString("Baseline", fnt, New SolidBrush(clr), _
                        0, yBaseline - cyAscent)
    End Sub
End Class

cyAscent値は、144ポイントのTimes New Romanフォントのアセントです。ベースライン座標からその値を差し引けば、残った差はベースライン上に描画されるテキストの位置を示すことになります

Dd297679.fig17-9(ja-jp,MSDN.10).gif

この画面を注意深く見ると、いくつかの文字の丸みを帯びた部分がベースラインより少し下に描かれていることがわかります。しかし、これで正常なのです。

17.12 | フォントファミリ配列

FontFamilyは、さらにもう1つの共有プロパティと共有メソッドを持っています。これらは、たいへん類似していますが、きわめて重要です。いずれもシステム内にインストールされているTrueTypeとOpenTypeフォントに対応するFontFamily配列を返してきます。共有プロパティを次に示します。

▼FontFamilyの共有プロパティ(抜粋)

プロパティ名 アクセス状態
Families FontFamily() Get

このプロパティは次のように使用できます。


Dim aff() As FontFamily = FontFamily.Families

このコードが実行されると、aff配列の個々の要素にはFontFamilyオブジェクトが格納されます。Win32 APIでフォントを列挙したことがある人にとっては、あまりにも簡単すぎて信じられないことでしょう。

このプロパティは本当にすばらしいですから、それぞれのフォントファミリからサンプルフォントを作成し、すべてのフォントをリスト表示したいと考える人もいることでしょう。次に紹介するサンプルプログラムは、まさにそのような機能を実装しています。


NaiveFamiliesList.vb
'--------------------------------------------------
' NaiveFamiliesList.vb (c) 2002 by Charles Petzold
'--------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class NaiveFamiliesList
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(New NaiveFamiliesList())
    End Sub

    Sub New()
        Text = "Naive Font Families List"
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Dim br As New SolidBrush(clr)
        Dim y As Single = 0
        Dim aff() As FontFamily = FontFamily.Families
        Dim ff As FontFamily

        For Each ff In aff
            'Try
            Dim fnt As New Font(ff, 12)
            grfx.DrawString(ff.Name, fnt, br, 0, y)
            y += fnt.GetHeight(grfx)
            'Catch
            'End Try
        Next ff
    End Sub
End Class

For Eachステートメントは、FontFamily配列内の各要素を繰り返し取得します。個々の要素から、Fontコンストラクタは12ポイントのフォントを作成した後、DrawStringメソッドを呼び出し、フォントファミリ名をそのフォントを使って表示します。GetHeight呼び出しは、次のテキストを表示する垂直座標を算出しています。

このプログラムは、皆さんのシステム上では動作しないかもしれません。私のシステムでは、Fontコンストラクタは配列内の最初のフォントファミリを処理する段階で、例外がスローされます。

私の環境で問題が生じている理由は、ヘブライ語のフォントファイルAhronbd.ttfがインストールされているためです。このファイルに実装されているフォントはAharoni Boldであり、Aharoniファミリの唯一のフォントです(少なくともWindows XPに関する限り)。このため、次のようなコードは正常に実行されます。


fnt = New Font("Aharoni", 12, FontStyle.Bold)

しかし、次のコードは動作しません。


fnt = New Font( "Aharoni", 12)

これは、次のコードと同じなのです。


fnt = New Font("Aharoni", 12, FontStyle.Regular)

意外なことに、Aharoniファミリには通常、標準フォントが存在しないのです。

この問題への対処法はいくつかあります。まず、TryとCatch構文が脳裏に浮かぶ人が、多いのではないでしょうか。先に紹介したFor EachループをTryブロック内に記述してしまうわけです。そして、Catchブロック内で問題を発生させたフォントファミリ名を表示するのです。次に紹介するコードでは、フォームのFontプロパティを使って、フォントファミリ名の前にアスタリスク(*)を付けて表示しています。


For Each ff in aff
    Try
        Dim fnt As New Font(ff, 12)
        grfx.DrawString(ff.Name, fnt, br, 0, y)
        y += fnt.GetHeight(grfx)
    Catch
        grfx.DrawString("*" & ff.Name, Font, br, 0, y)
        y += Font.GetHeight(grfx)
    End Try
Next ff

しかし、別の解決法がある場合、TryとCatchブロックは避けるのが一般的です。この場合、FontFamilyクラスのIsStyleAvailableメソッドが利用できます。次に紹介するプログラムは、このメソッドを応用し、Aharoni Boldフォントがインストールされているかどうかに関係なく、フォントファミリをリスト表示します。


BetterFamiliesList.vb
'---------------------------------------------------
' BetterFamiliesList.vb (c) 2002 by Charles Petzold
'---------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class BetterFamiliesList
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(New BetterFamiliesList())
    End Sub

    Sub New()
        Text = "Better Font Families List"
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Dim br As New SolidBrush(clr)
        Dim y As Single = 0
        Dim aff() As FontFamily = FontFamily.Families
        Dim ff As FontFamily

        For Each ff In aff
            If ff.IsStyleAvailable(FontStyle.Regular) Then
                Dim fnt As New Font(ff, 12)
                grfx.DrawString(ff.Name, fnt, br, 0, y)
                y += fnt.GetHeight(grfx)
            Else
                grfx.DrawString("* " & ff.Name, Font, br, 0, y)
                y += Font.GetHeight(grfx)
            End If
        Next ff
    End Sub
End Class

しかし、システムに多数のフォントをインストールしている場合には、このアプローチではすべてのフォントファミリのリストを画面に表示させることはできないでしょう(リストを印字したり、大きなモニタを持っていても無理な場合もあるでしょう)。

スクロールバーを追加していない場合には、リストに書式を付け、列単位で整理してしまうとよいでしょう。この方法を応用するサンプルプログラムを次に示します。表示が見にくい場合、iPointSizeフィールド値を10、8、6に変更してください。


FamiliesList.vb
'---------------------------------------------
' FamiliesList.vb (c) 2002 by Charles Petzold
'---------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class FamiliesList
    Inherits PrintableForm

    Const iPointSize As Integer = 12

    Shared Shadows Sub Main()
        Application.Run(New FamiliesList())
    End Sub

    Sub New()
        Text = "Font Families List"
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Dim br As New SolidBrush(clr)
        Dim x As Single = 0
        Dim y As Single = 0
        Dim fMaxWidth As Single = 0
        Dim aff() As FontFamily = GetFontFamilyArray(grfx)
        Dim ff As FontFamily

        For Each ff In aff
            Dim fnt As Font = CreateSampleFont(ff, iPointSize)
            Dim szf As SizeF = grfx.MeasureString(ff.Name, fnt)
            fMaxWidth = Math.Max(fMaxWidth, szf.Width)
        Next ff

        For Each ff In aff
            Dim fnt As Font = CreateSampleFont(ff, iPointSize)
            Dim fHeight As Single = fnt.GetHeight(grfx)

            If y > 0 AndAlso y + fHeight > cy Then
                x += fMaxWidth
                y = 0
            End If
            grfx.DrawString(ff.Name, fnt, br, x, y)
            y += fHeight
        Next ff
    End Sub

    Protected Overridable Function GetFontFamilyArray _
                                (ByVal grfx As Graphics) As FontFamily()
        Return FontFamily.Families
    End Function

    Private Function CreateSampleFont(ByVal ff As FontFamily, _
                                      ByVal fPointSize As Single) As Font
        If ff.IsStyleAvailable(FontStyle.Regular) Then
            Return New Font(ff, fPointSize)
        ElseIf ff.IsStyleAvailable(FontStyle.Bold) Then
            Return New Font(ff, fPointSize, FontStyle.Bold)
        ElseIf ff.IsStyleAvailable(FontStyle.Italic) Then
            Return New Font(ff, fPointSize, FontStyle.Italic)
        Else
            Return Font
        End If
    End Function
End Class

DoPageメソッドでは、2つのFor Eachループが使われています。最初のループでは、使用するサンプルフォントのフォントファミリ名を決め、その最大幅を保存しています。2番目のループでは、その幅を実際に使って、複数の列を表示しています。

ここでは、クラスの最後の方に置かれているCreateSampleFontメソッドに注意してください。このメソッドは、Fontコンストラクタではなく、DoPageメソッド内で使用しています。このメソッドは、IsStyleAvailableメソッドを内部で呼び出し、標準、太字、あるいは斜体フォントを作成できるかどうかを判定しています。このアプローチは、先のサンプルプログラムと異なり、Aharoniフォントを表示することができます。実行例を次に示します。

Dd297679.fig17-10(ja-jp,MSDN.10).gif

極東地域や中東地域で使用されているフォントのいくつかをインストールしている場合、行間隔が大きくなっている印象を受けると思います。それは、これらのフォントがラテン以外のアルファベットを表示するように設計されているためです。

このサンプルプログラムでは、FontFamilyオブジェクト配列を取得するコードを、GetFontFamilyArrayという小さなメソッド内に整理し、他のコードから切り離しています。この理由は、次に紹介するサンプルプログラムを見るとはっきりわかります。次のサンプルプログラムでは、FontFamilyに実装されている共有メソッドだけを使って、同じ機能を実現しています。その共有メソッドは、Graphics型の引数を受け取るだけで、Familiesプロパティと同じなのです。後でじっくり比べてみてください。

▼FontFamilyの共有メソッド

FontFamily() GetFamilies(Graphics grfx)

このメソッド提供の背景には、グラフィック出力デバイスが異なれば、インストールされているフォントも異なる、という考え方があります。特に、一部のプリンタでは、画面上に表示できないような内蔵フォントを持っています。これから紹介するサンプルプログラムでは、Familiesではなく、GetFamiliesからフォントファミリを取得できるように、GetFontFamilyArrayをオーバーライドしています。


GetFamiliesList.vb
'------------------------------------------------
' GetFamiliesList.vb (c) 2002 by Charles Petzold
'------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class GetFamiliesList
    Inherits FamiliesList

    Shared Shadows Sub Main()
        Application.Run(New GetFamiliesList())
    End Sub

    Sub New()
        Text = "Font GetFamilies List"
    End Sub

    Protected Overrides Function GetFontFamilyArray _
                                (ByVal grfx As Graphics) As FontFamily()
        Return FontFamily.GetFamilies(grfx)
    End Function
End Class

少なくとも私のプリンタに関する限り、GetFamiliesはFamiliesと同じ配列を返してきます。おそらく、後のバージョンのWindowsフォームでは、より多くのプリンタ専用フォントがサポートされることになるでしょう。

17.13 | フォントコレクション

FontFamilyクラスのFamiliesプロパティとGetFamiliesメソッドは、フォントファミリを取得するための唯一の手段ではありません。System.Drawing.Text名前空間には、FontCollectionというクラスが定義されており、このクラスからInstalledFontCollectionとPrivateFontCollectionという2つのクラスが派生しています。

FontCollectionが実装しているプロパティは、次の1つだけです。

▼FontCollectionのプロパティ

プロパティ名 アクセス状態
Families FontFamily() Get

このプロパティは、Sharedとして定義されているわけではありませんが、Installed FontCollectionとPrivateFontCollectionクラスのいずれにも継承されています。次に紹介するサンプルプログラムは、FamiliesListプログラム内のGetFontFamilyArrayメソッドをオーバーライドしています。ソースコードからわかるように、Installed>Font Collectionクラスのインスタンスを作成し、そのオブジェクトのFamiliesプロパティを使ってフォントファミリ配列を取得しています。


InstalledFontsList.vb
'---------------------------------------------------
' InstalledFontsList.vb (c) 2002 by Charles Petzold
'---------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Drawing.Text
Imports System.Windows.Forms

Class InstalledFontsList
    Inherits FamiliesList

    Shared Shadows Sub Main()
        Application.Run(New InstalledFontsList())
    End Sub

    Sub New()
        Text = "InstalledFontCollection List"
    End Sub

    Protected Overrides Function GetFontFamilyArray _
                                (ByVal grfx As Graphics) As FontFamily()
        Dim fc As New InstalledFontCollection()
        Return fc.Families
    End Function
End Class

このサンプルプログラムは、FamiliesListサンプルプログラムと同じ結果を表示します。

PrivateFontCollectionクラスのインスタンスを作成したとき、当初は、コレクションオブジェクトにはフォントファミリが含まれていません。次の2つのメソッドを使って、プログラマがコレクションオブジェクトにフォントを追加する必要があります。

▼PrivateFontCollectionのメソッド(抜粋)

Sub AddFontFile(ByVal strFilename As String) Sub AddMemoryFont(ByVal pFont As IntPtr, ByVal iLength As Integer)

これらのメソッドは、アプリケーションが独自のフォントファイルを使用しているような場面で使用されます。PrivateFontCollectionオブジェクトを作成し、これらの2つのメソッドを呼び出します。その後、アプリケーションは、Familiesプロパティを使用して、Fontオブジェクトを作成するために適切なFontFamilyオブジェクト配列を取得します。あるいは、プログラム作成者は、どのようなフォントファミリがコレクション内に含まれているのか知っているはずですから、「17.10 FontFamilyによる新規フォントの作成」のFontFamilyコンストラクタの表で紹介した3番目のコンストラクタを使用し、コレクション内のフォントファイルからFontFamilyオブジェクトを作成することもできるでしょう。

17.14 | 多種多様なDrawStringメソッド

私たちはこれまで、6種類のDrawStringメソッドについて学びました。

▼GraphicsのDrawStringメソッド

Sub DrawString(ByVal str As String, ByVal fnt As Font,                ByVal br As Brush, ByVal ptf As PointF) Sub DrawString(ByVal str As String, ByVal fnt As Font,                ByVal br As Brush, ByVal x As Single,                ByVal y As Single) Sub DrawString(ByVal str As String, ByVal fnt As Font,                ByVal br As Brush, ByVal rectf As RectangleF) Sub DrawString(ByVal str As String, ByVal fnt As Font,                ByVal br As Brush, ByVal ptf As PointF,                ByVal sf As StringFormat) Sub DrawString(ByVal str As String, ByVal fnt As Font,                ByVal br As Brush, ByVal x As Single,                ByVal y As Single, ByVal sf As StringFormat) Sub DrawString(ByVal str As String, ByVal fnt As Font,                ByVal br As Brush, ByVal rectf As RectanglF,                ByVal sf As StringFormat)

PointF構造体を使用するメソッドバージョンは、2つのSingle値を使用するバージョンと機能的には同じです。単に文字列を表示する始点を、2つの方法で指定しているだけです。PointFか2つのSingle値を使用する4つのすべてのオーバーロードDrawStringバージョンは、一般的には、1行のテキストを表示します。しかし、そのテキスト内にラインフィード文字(Chr(10)かvbLf)を含んでいる場合、それに続くテキストは次の行に表示されます。

RectangleF引数を受け取る2つのDrawStringバージョンは、幅が大きすぎるテキストを矩形領域幅に合わせて折り返してくれます。矩形領域幅を超える幅を持つ単語の場合、メソッドは可能な限り調整するように動作しますが、折り返せない部分は次の行に表示します。

これらのDrawStringバージョンは、Unicode文字&H00A0を、非ブレークスペースとして適切に処理してくれます。スペースで折り返されると、本来の意味が薄れるような心配があるテキストを用意しているような場合、通常のスペースの代わりに、この非ブレークスペースを使用するとよいでしょう。次にテキスト例を示します。


World War & ChrW(&H00A0) & "I"

この場合、文字Iを行末に表示できないときには、DrawStringメソッドはWorldの直後で行を分割するはずです。

これらのDrawStringメソッドは、ソフトハイフンを表すUnicode文字&H00ADを適切に認識しません。私たちは習慣として、綴りの長い単語では、次のように音節の区切りでソフトハイフンを使用しています。


ex & ChrW(&H00AD) & "traor" & ChrW(&H00AD) & "di" & ChrW(&H00AD) & "nary"

理論的には、テキストフォーマット機能がハイフンで行を分割する場合、適切に行が分割され、ハイフンが表示されるはずです。そのような機能を持っていない場合、ハイフンは表示されないはずです。DrawStringメソッドは、このようなフォーマット機能に関係なくハイフンを表示し、ハイフンの直前で行を分割します。

これらのDrawStringバージョンは、非ブレークハイフンのUnicode文字&H2011も適切に処理できません。たとえば、私たちは、次のようなコードを使用することがあります。


T & ChrW(&H2011) & "shirt"

Tの直後にハイフンが付いた状態で行末に置かれた場合には、奇妙な印象を与えてしまいます。TrueTypeフォントによっては、この文字をサポートしていません。Lucida Sans Unicodeをはじめとするフォントはこの文字をサポートしていませんが、DrawStringメソッドはそれらのフォントが使用されている場合、非ブレークハイフン上で行分割をせず、ハイフンの直後にいくつかの余分なスペースを挿入しています。

DrawStringメソッドのワードラップ機能は強力な印象を受けますが、その限界を知っておくことも同じように重要といえます。たとえば、アプリケーションから特定のテキストブロックを表示する場合には、DrawStringメソッドは理想的な機能を提供していると思うかもしれません。しかし、多少の問題を抱えていることも事実なのです。太字か斜体で表示したい単語を含むテキスト文字列があったとしましょう。実は、その単語を希望どおりに表示できないのです。DrawStringメソッドが受け取るFont引数は、テキスト全体のフォントを決定します。しかし、さいわいなことに、DrawString呼び出し時にテキストに下線を引く方法は、1つ用意されています。詳細については、この後でUnderlinedTextサンプルプログラムを紹介するときに説明します。

既にご存知かもしれませんが、StringFormatクラスのAlignmentプロパティを使用すれば、矩形領域内に表示するテキストの水平方向の位置を制御することができます。矩形領域の左端に沿って段落を揃えたり、右辺に沿って揃えたり、あるいは矩形領域の中心に段落を整頓させるようなこともできます。しかし、矩形領域に合わせて整列させるようなことはできません。これが2番目の制限です。DrawStringメソッドでは、単語と単語の間に余分なスペースを挿入し、左右の余白を均衡させるようなことはできないのです。

これらの機能のいずれかを使用する必要がある場合、2つの選択肢があります。独自のテキストフォーマットロジックを作成するか、あるいは、DrawStringメソッド以上のフォーマットオプションを持つRichTextBoxコントロールを使用することができます。このコントロールについては第11章で詳述します。

17.15 | アンチエイリアシングテキスト

第15章では、GraphicsクラスのSmoothingModeとPixelOffsetModeプロパティを使った、直線と曲線の表示制御方法を具体的に説明しました。Windowsでは、テキスト表示にアンチエイリアシング機能を使用できるようになっています。この機能は、私たちユーザーが制御できます。設定するには、[画面のプロパティ]ダイアログボックスを開き、[デザイン]タブの[効果]をクリックし、ダイアログボックスの[次の方法でスクリーンフォントの縁を滑らかにする]を切り替えます。

必要に応じてGraphicsクラスのTextRenderingHintプロパティを変更し、ユーザーの好みをオーバーライドすることができます。

▼Graphicsのプロパティ(抜粋)

プロパティ名 アクセス状態
TextRenderingHint TextRenderingHint Get/Set

返される値は、System.Drawing.Text名前空間に列挙体として定義されています。

▼TextRenderingHint列挙体

メンバ 意味
SystemDefault 0 既定
SingleBitPerPixelGridFit 1 グリッドに合わせ、アンチエイリアシング無効
SingleBitPerPixel 2 グリッドに合わせず、アンチエイリアシング無効
AntiAliasGridFit 3 グリッドに合わせ、アンチエイリアシング有効
AntiAlias 4 グリッドに合わせず、アンチエイリアシング有効
ClearTypeGridFit 5 LCD表示用のClearType選択

ClearTypeは、アンチエイリアシングに似た機能ですが、LCDディスプレイのカラードット配列を活用します ※4。

※4 この技術は、20年以上にわたり研究されてきました。詳細については、Steve GibsonのWebサイト(http://grc.com/cleartype.htm)を参照してください。

次に紹介するサンプルプログラムは、これらの6個の列挙体値すべてを応用する例です。


AntiAliasedText.vb
'------------------------------------------------
' AntiAliasedText.vb (c) 2002 by Charles Petzold
'------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Drawing.Text
Imports System.Windows.Forms

Class AntiAliasedText
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(new AntiAliasedText())
    End Sub

    Sub New()
        Text = "Anti-Aliased Text"
        Font = new Font("Times New Roman", 12)
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Dim br As New SolidBrush(clr)
        Dim str As String = "A "
        Dim cxText As Integer = CInt(grfx.MeasureString(str, Font).Width)
        Dim i As Integer

        For i = 0 To 5
            grfx.TextRenderingHint = CType(i, TextRenderingHint)
            grfx.DrawString(str, Font, br, i * cxText, 0)
        Next i
    End Sub
End Class

このサンプルプログラムは、6種類のTextRenderingHint値を使って大文字のAを表示しています。表示内容をAlt+PrintScreenキーを押してクリップボードにコピーし、ペイントなどに貼り付けて拡大してみてください。貼り付けたイメージを拡大したのが次のものです。

Dd297679.fig17-145(ja-jp,MSDN.10).gif

使用中の環境の設定にもよりますが、SystemDefaultはSingleBitPerPixelGridFitかAntiAliasGridFitに一致しているはずです。ClearTypeを選択している場合、1番目の大文字AのイメージはClearTypeGridFitに一致しているはずです。SingleBitPerPixel GridFitとSingleBitPerPixelの列挙体値はいずれも、アンチエイリアシングを無効にします。しかし、3番目の大文字Aのイメージには、1つ以上の空白ピクセルが表示されていることに注意してください。キャラクタラスタライザは、ピクセルに色を付けるかどうかを判断するために、より厳しい基準を使っています。

4番目(AntiAliasGridFit)と5番目(AntiAlias)のイメージは、アンチエイリアシングの使用例です。ピクセルは、より暗い灰色の陰で色付けされています。ただし、その濃度は、理論的な輪郭文字との交差度合いに応じて決まります。

ClearTypeGridFit値の場合、ストロークの左右のピクセルは、LCDディスプレイのカラードットの水平配置に応じて異なる色付けが施されます。通常のCRTディスプレイ上では、ClearTypeオプションを選択すべきではありません。いくつかのLCDディスプレイでは、90°回転させ、ポートレートモードで表示することができます。その場合、ClearTypeは機能しません。

17.16 | 文字列の大きさの測定

私たちは、既に第3章よりMeasureStringメソッドを使用し、テキスト文字列の長さを知ったうえで、その文字列を正確な位置に表示してきました。実は、このメソッドは、別の目的にも使用できます。気付いている方もいるかもしれませんが、DrawStringメソッドは目的の矩形領域の背景を消去することなく、テキストを表示しています。ただし、これはWindows GDI+の既定の振る舞いではありません。仮に、背景を消去する必要がある場合、FillRectangle呼び出し時に、MeasureStringメソッドから返されるSizeFと、テキストを表示する座標値を組み合わせて使用することができます。

MeasureStringメソッドには、次のような7種類のバージョンが用意されています。

▼GraphicsのMeasureStringメソッド

Function MeasureString(ByVal str As String, ByVal fnt As Font) As SizeF Function MeasureString(ByVal str As String, ByVal fnt As Font,                        ByVal iWidth As Integer) As SizeF Function MeasureString(ByVal str As String, ByVal fnt As Font,                        ByVal szf As SizeF) As SizeF Function MeasureString(ByVal str As String, ByVal fnt As Font,                        ByVal iWidth As Integer,                        ByVal strfmt As StringFormat) As SizeF Function MeasureString(ByVal str As String, ByVal fnt As Font,                        ByVal szf As SizeF,                        byVal strfmt As StringFormat) As SizeF Function MeasureString(ByVal str As String, ByVal fnt As Font,                        ByVal ptfOrigin As PointF,                        ByVal strfmt As StringFormat) As SizeF Function MeasureString(ByVal str As String, ByVal fnt As Font,                        ByVal szf As SizeF,                        ByVal strfmt As StringFormat,                        ByRef iCharacters As Integer,                        ByRef iLines As Integer) As SizeF

私たちがこれまで使ってきたMeasureStringは、この表の1番目のバージョンです。このバージョンのメソッドは、指定されたフォントで表示された文字列の幅と高さを返します。返されるSizeFオブジェクトのHeightプロパティは、FontクラスのGetHeightメソッドが返す値としばしば近いことがあります。しかし、テキスト内にラインフィード文字が含まれている場合には、フォントの高さの整数倍になることがありえます。

2番目のバージョンは、テキスト幅を示す第3の引数を受け取ります。このバージョンは、DrawStringのRectangleFバージョンで文字列を表示し、右端折り返しを応用したいような場面で有効です。MeasureStringが返すSizeFオブジェクトのWidthプロパティ値は、常にiWidth引数と等しいかそれ以下となっています。Heightプロパティ値は、GetHeight値で割ると、行数に等しくなります。

3番目のバージョンは、幅と高さを示すSizeF引数を受け取ります。受け取ったSizeF引数のWidthプロパティ値が2番目のバージョンで使用されているiWidth値と同じで、Heightプロパティが文字列内のすべてのテキスト行数以上となっている場合、このバージョンのメソッドから返される値は2番目のバージョンのものと同じになります。それ以外の場合には、返されるSizeFオブジェクトのHeightプロパティは、SizeF引数のHeightプロパティと同じになり、Widthプロパティは、その大きさを持つ矩形領域に収まる最大テキスト幅を示します。

4、5、6番目のバージョンは、2、3番目のバージョンと同じですが、さらにStringFormat引数を受け取ります。DrawString呼び出し内でStringFormat引数を使用する場合、MeasureString呼び出し時にも使用した方がよいでしょう。

最後のMeasureStringメソッドは、返される追加情報を受け取るための2つの引数を持っています。(最後の2つの引数がByVal(値返し)ではなくByRef(参照返し)として定義されている点に注意してください)。SizeF構造体と同じ大きさのRectangleF構造体と、同一のString Formatオブジェクトを渡されたときにDrawStringが表示するテキストの文字数と行数が、これらの引数に返されます。

引数を付けてMeasureStringメソッドを呼び出すことは、1つのテキストブロックを複数のDrawStringメソッド経由で表示するような場面で、きわめて有効な方法です。たとえば、DrawStringメソッドで1ページに収まりきれないほどのテキストを、プリンタに打ち出す場面を考えてみましょう。まず、MeasureStringメソッドを呼び出し、最初のページに印字できるテキスト量を決めます。次に、その返される値を基にして、新しいテキスト文字列を2ページ目に印刷するような応用ができます。より詳細な説明については、本章の最後で紹介するTextColumnsサンプルプログラムのMeasureStringメソッドを解説するときに行います。

17.17 | StringFormatオプション

DrawStringとMeasureStringメソッドは、StringFormat型のオブジェクトをオプション引数として受け取ることができます。この引数は、テキスト表示にいろいろな(時には些細な、しかし時には重要な意味を持つ)バリエーションを加えることができます。次のコンストラクタのいずれかを使用すれば、StringFormatオブジェクトを作成することができます。

▼StringFormatのコンストラクタ

StringFormat() StringFormat(ByVal strfmt As StringFormat) StringFormat(ByVal sff As StringFormatFlags) StringFormat(ByVal sff As StringFormatFlags, ByVal iLanguage As Integer)

2番目のバージョンのメソッドは、実質的には、既存のStringFormatオブジェクトのクローンを作成します。3番目と4番目のバージョンは、StringFormatFlags列挙体値の組み合わせ値に基づいて、StringFormatオブジェクトを作成します。StringFormatFlags列挙体は、StringFormatのFormatFlagsプロパティの設定処理でも使用されます。

▼StringFormatのプロパティ(抜粋)

プロパティ名 アクセス状態
FormatFlags StringFormatFlags Get/Set

StringFormatFlags列挙体は、一連のビットフラグで構成されます。

▼StringFormatFlags列挙体

メンバ
DirectionRightToLeft &H0001
DirectionVertical &H0002
FitBlackBox &H0004
DisplayFormatControl &H0020
NoFontFallback &H0400
MeasureTrailingSpaces &H0800
NoWrap &H1000
LineLimit &H2000
NoClip &H4000

本書では、特定のサンプルプログラムを取り上げながら、これらのフラグのいくつかを紹介したいと思います。第5章で紹介したTypeAwayサンプルプログラムでは、MeasureTrailingSpacesフラグを使っています。そして、このフラグは、この後で紹介するBoldAndItalicTighterというサンプルプログラム内でも使用されます。さらに、同じくこの後で紹介するTrimmingTheTextサンプルプログラム内では、NoWrapとNoClipフラグの使用方法を取り上げます。

既定コンストラクタでStringFormatオブジェクトを新規に作成すると、FormatFlagsプロパティは0に設定されます。ここで注目してもらいたいのは、これらの列挙体値は相互に独立したビットフラグですから、Visual Basicの論理和(OR)演算子で組み合わせることができるということです。たとえば、次のようなコーディングが可能です。


Dim strfmt As New StringFormat(StringFormatFlags.DirectionVertical Or _
                               StringFormatFlags.NoClip)

FormatFlagsプロパティを設定するときには、次のように、Or演算子を使用して既存のフラグを新しいフラグと結合する習慣を身に付けるとよいでしょう。


strfmt.FormatFlags = strfmt.FormatFlags Or StringFormatFlags.NoWrap

このようにすれば、以前に設定している他のフラグを間違って無効にしてしまうような事態を回避できます。

コンストラクタの1つを使用するほかに、次のような共有プロパティの1つを呼び出し、StringFormatオブジェクトを取得することもできます。

▼StringFormatの共有プロパティ

プロパティ名 アクセス状態
GenericDefault StringFormat Get
GenericTypographic StringFormat Get

これらのプロパティから返されるStringFormatオブジェクトのプロパティを調べてみると、GenericDefaultは既定コンストラクタと同じStringFormatオブジェクトを返してくることがわかります。一方、GenericTypographicプロパティは、FitBlackBox、LineLimit、NoClipの3つのフラグがセットされているオブジェクトを返します。しかし、このオブジェクトは、後で紹介するTrimmingプロパティとは異なる値を持っています。

ところが、GenericTypographicから返されるStringFormatオブジェクトは、DrawStringとMeasureStringの動作に影響を与える情報を持っています。これらの情報は、StringFormatオブジェクトのプロパティとフラグから知ることはできません。詳細については後述します。

17.18 | グリッド調整とテキスト調整

GDI+のテキスト処理部は、デバイスに依存しないように設計されています。実際上、MeasureStringメソッドは、出力デバイスから独立しているテキストサイズを返します。画面とプリンタ上で同一のページ変換を設定している場合、MeasureStringはすべてのテキスト文字列とフォントに関して同一の値を返してきます。この一貫性はテキスト表現上の一貫性を意味し、画面上のテキストフォーマットとプリンタ上の印字フォーマットを簡単に統一できるようにしています。

WYSIWYGの最終目的は、多くの人から歓迎されていますが、実際には簡単に達成できるものではありません。問題はピクセルなのです。アウトラインフォントの文字がラスタライズされるとき、元の浮動小数点座標は個別のピクセルに丸め上げる必要があります。これは、「グリッド調整(grid fitting)」と呼ばれています。この調整では、フォントの可読性を損なうことがないように、丸め演算用のヒント情報を必要とします。たとえば、大文字Hの垂直方向の2つのストロークは、同じ幅でなければなりません。小さなポイントサイズの場合、これらの2つのストロークは少なくとも1ピクセル幅で、さらに分離用のピクセルも必要になります。出力デバイスと比較してポイントサイズがきわめて小さい場合、ほとんど判読できないと思いますので、そのような付加条件は無視できます。

いくつかの場面では、特にビデオディスプレイのような低解像度デバイス上で小さなポイントサイズを使っている場合、グリッド調整により、描画文字が理論的なサイズ以上に目に見えて拡大してしまうことがあります。これらの多数の文字(たとえばArialのiなど)で構成される文字列は、画面上ではプリンタ上の印字文字以上の大きさになります。この種の問題例の詳細については、https://www.gotdotnet.com/team/windowsforms/gdiptext.aspxを参照してください。

DrawStringとMeasureStringを使って、いくつかのテキストを連結する場合(既に紹介したBoldAndItalicサンプルプログラムのように)、テキスト間に余分なスペースが挿入されている状態と、テキストがオーバーラップされている状態のどちらを選択するのでしょうか。ほとんどの方は、オーバーラップされた状態を選択することはないと思います。テキストのオーバーラップを防止するために、DrawStringとMeasureStringメソッドは余分なスペースを含むように微調整されています。このため、ラスタライザが特定のフォントを描画する余分なスペースを要求する場合、そのスペースはそのまま有効となってしまいます(結果的にサイズが大きくなります)。

既定では、MeasureStringから返されるSizeFオブジェクトは、理論的な大きさよりも1/8em大きな値をHeightプロパティに設定すると共に、Widthプロパティに対しては、数%幅を拡大したうえで、理論値より1/3em大きい値を設定しています。念のために説明しておきますが、emはフォントのポイントサイズと同じです。たとえば、24ポイントのフォントの場合、1/3emは8ポイントを意味します。DrawStringメソッドは、既定では、指定された垂直座標より1/6em上にある位置からテキスト表示を開始します。このため、MeasureStringは、理論的なテキスト文字列より、左右に1/6em大きくなった矩形領域の大きさを返します。

このような理由から、BoldAndItalicサンプルプログラムや、サンプルとしてより優れていると思われるBoldAndItalicBiggerプログラムは、連結されたテキスト間に過剰なほどのパディングを挿入しているのです。

グリッド調整の問題は、低解像度デバイス上に表示される、小さなポイントサイズを持つフォントだけに影響を与えることを忘れないでください。ところが、デバイスからの独立を達成するため、DrawStringとMeasureStringメソッドに組み入れられている余分なパディングは、低解像度と高解像度の両デバイスで同じとなっている必要があるのです。さらに、この埋め草は、小さいフォントと大きいフォントでつりあっている必要もあるのです。たとえば、720ポイントのフォントの場合、Measure

Stringメソッドは、7.2ポイントのフォントに返す値より、100倍のテキストサイズを返す必要があります。それでは、このような余分なスペースの挿入を望まない場合、どのような対策を取ることができるでしょうか? 実は、StringFormat.GenericTypographicから、StringFormatオブジェクトを単純に作成すればよいのです。次に紹介するBoldAndItalicTighterサンプルプログラムは、このようなStringFormatオブジェクトを内部で使用しています。


BoldAndItalicTighter.vb
'-----------------------------------------------------
' BoldAndItalicTighter.vb (c) 2002 by Charles Petzold
'-----------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Drawing.Text
Imports System.Windows.Forms

Class BoldAndItalicTighter
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(new BoldAndItalicTighter())
    End Sub

    Sub New()
        Text = "Bold and Italic (Tighter)"
        Font = new Font("Times New Roman", 24)
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Const str1 As String = "This is some "
        Const str2 As String = "bold"
        Const str3 As String = " text, and Me is some "
        Const str4 As String = "italic"
        Const str5 As String = " text."
        Dim br As New SolidBrush(clr)
        Dim fntRegular As Font = Font
        Dim fntBold As New Font(fntRegular, FontStyle.Bold)
        Dim fntItalic As New Font(fntRegular, FontStyle.Italic)
        Dim ptf As New PointF(0, 0)
        Dim strfmt As StringFormat = StringFormat.GenericTypographic
        strfmt.FormatFlags = strfmt.FormatFlags Or _
                            StringFormatFlags.MeasureTrailingSpaces

        grfx.DrawString(str1, fntRegular, br, ptf, strfmt)
        ptf.X += grfx.MeasureString(str1, fntRegular, ptf, strfmt).Width

        grfx.DrawString(str2, fntBold, br, ptf, strfmt)
        ptf.X += grfx.MeasureString(str2, fntBold, ptf, strfmt).Width

        grfx.DrawString(str3, fntRegular, br, ptf, strfmt)
        ptf.X += grfx.MeasureString(str3, fntRegular, ptf, strfmt).Width

        grfx.DrawString(str4, fntItalic, br, ptf, strfmt)
        ptf.X += grfx.MeasureString(str4, fntItalic, ptf, strfmt).Width

        grfx.DrawString(str5, fntRegular, br, ptf, strfmt)
    End Sub
End Class

このプログラムでは、MeasureTrailingSpacesフラグも設定していることに注意してください。実行画面は次のようになります。

Dd297679.fig17-11(ja-jp,MSDN.10).gif

実は、このサンプルプログラムでは、GenericTypographicオブジェクトを使用しなくても同じことができます。というのは、大きなフォントが使われ、ピクセル間の違いが出ないことがはっきりしているからです。ビデオディスプレイ上の小さなフォントサイズでGenericTypographicを使用したい場合、アンチエイリアシングも有効にしておくとよいでしょう。アンチエイリアシングを有効にしておけば、個々のピクセルは理論的な輪郭との交点を基に色付けされますから、既に述べたグリッド調整の影響は排除されます。

17.19 | 水平/垂直方向の配置

本書で最初にStringFormatクラスを紹介したのは第3章です。そこでは、このクラスのオブジェクトを作成し、フォームのクライアント領域の中心に目的のテキストを表示しました。テキストの表示位置を揃える場合、次に示す2種類のStringFormatプロパティを使用できます。

▼StringFormatのプロパティ(抜粋)

プロパティ名 アクセス状態 意味
Alignment StringAlignment Get/Set 水平方向の配置
LineAlignment StringAlignment Get/Set 垂直方向の配置

これらのプロパティは、いずれもStringAlignment型です。この型は、次のような3つのメンバを持つ列挙体です。

▼StringAlignment列挙体

メンバ 意味
Near 0 通常は、左または上端
Center 1 常に中央
Far 2 通常は、右または下端

これらの配置値は、DrawString呼び出し内でPointFオブジェクトを指定するか、あるいはRectangleFオブジェクトを指定するかに応じて異なる意味を持ちます。まずは、DrawString内でRectangleFオブジェクトを使用する例を見てみましょう。次に紹介するサンプルプログラムは、9回のDrawString呼び出しを行い、その中でクライアント領域の矩形を使っています。また、それぞれの呼び出し時には、AlignmentとLineAlignmentプロパティの組み合わせに変化を持たせています。多少おもしろみをつけるために、表示するテキスト内にラインフィード文字を含めています。


StringAlignmentRectangle.vb
'---------------------------------------------------------
' StringAlignmentRectangle.vb (c) 2002 by Charles Petzold
'---------------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class StringAlignmentRectangle
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(New StringAlignmentRectangle())
    End Sub

    Sub New()
        Text = "String Alignment (RectangleF in DrawString)"
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Dim br As New SolidBrush(clr)
        Dim rectf As New RectangleF(0, 0, cx, cy)
        Dim astrAlign() As String = {"Near", "Center", "Far"}
        Dim strfmt As New StringFormat()
        Dim iVert, iHorz As Integer

        For iVert = 0 To 2
            For iHorz = 0 To 2
                strfmt.LineAlignment = CType(iVert, StringAlignment)
                strfmt.Alignment = CType(iHorz, StringAlignment)
                grfx.DrawString( _
                    String.Format("LineAlignment = {0}" & vbLf & _
                                  "Alignment = {1}", _
                                  astrAlign(iVert), astrAlign(iHorz)), _
                    Font, br, rectf, strfmt)
            Next iHorz
        Next iVert

    End Sub
End Class

Alignmentプロパティには、3種類の値を設定できます。設定値に応じてテキストは、矩形領域内で、左揃え、中央寄せ、右揃えとして表示されます。LineAlignmentプロパティにも同じように3種類の値を設定し、テキストを矩形領域の上端、中央、下端位置に表示させることができます。実行結果を次に示します。

Dd297679.fig17-12(ja-jp,MSDN.10).gif

ところが、このような複数のDrawStringメソッドを実行する場面でPointFオブジェクトを使用すると、目的の文字はこの画面のように秩序正しく表示されることはありません。組み合わせによっては、文字がオーバーラップしてしまいます。それでは、一度に2、3の組み合わせ使って、テキストを表示させてみましょう。

次のサンプルプログラムは、DrawString呼び出し内で、PointFオブジェクトをクライアント領域の中央に設定していますが、4種類のAlignmentとLineAlignmentだけを使っています。


StringAlignmentPoint.vb
'-----------------------------------------------------
' StringAlignmentPoint.vb (c) 2002 by Charles Petzold
'-----------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class StringAlignmentPoint
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(new StringAlignmentPoint())
    End Sub

    Sub New()
        Text = "String Alignment (PointF in DrawString)"
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Dim br As New SolidBrush(clr)
        Dim pn As New Pen(clr)
        Dim astrAlign() As String = {"Near", "Center", "Far"}
        Dim strfmt As New StringFormat()
        Dim iVert, iHorz As Integer

        grfx.DrawLine(pn, 0, cy \ 2, cx, cy \ 2)
        grfx.DrawLine(pn, cx \ 2, 0, cx \ 2, cy)

        For iVert = 0 To 2 Step 2
            For iHorz = 0 To 2 Step 2
                strfmt.LineAlignment = CType(iVert, StringAlignment)
                strfmt.Alignment = CType(iHorz, StringAlignment)
                grfx.DrawString( _
                    String.Format("LineAlignment = {0}" & vbLf & _
                                  "Alignment = {1}", _
                                  astrAlign(iVert), astrAlign(iHorz)), _
                    Font, br, cx \ 2, cy \ 2, strfmt)
            Next iHorz
        Next iVert
    End Sub
End Class

2つのForステートメントに注意してください。iVertとiHorz変数は、最終的に0と2に設定されています。そして、プログラムは、AlignmentとLineAlignmentプロパティの4種類の組み合わせのみを使用し、次のようにテキストを表示しています。

Dd297679.fig17-13(ja-jp,MSDN.10).gif

DrawStringに渡されるPointFオブジェクトは、クライアント領域の中央を指定しています。DrawString呼び出しは、AlignmentとLineAlignmentプロパティの設定に応じて、クライアント領域の中央位置を基準に、2行のテキストの表示位置を決めています。

最初のForステートメントを次のように変更してみてください。


For iVert = 1 To 2 Step 2

再コンパイルして実行すると、LineAlignmentプロパティがStringAlignmentCenterに設定されるため、垂直座標はテキストの中心を指定していることになります。この場合、垂直座標は2つのテキスト行の垂直方向の中央を指定しているのです。実行画面を次に示します。

Dd297679.fig17-14(ja-jp,MSDN.10).gif

今度は、次のように2番目のForステートメントを変更しましょう。


For iHorz = 1 To 2 Step 2

再コンパイルして実行すると、2行のテキスト行の中央位置は水平座標によって決められます。実行結果を次に示します。

Dd297679.fig17-15(ja-jp,MSDN.10).gif

このような要領で2つのForステートメントを同時に変更すると、最終的な画面が得られます。つまり、2行のテキスト行は、クライアント領域の中点を中心にして、水平方向と垂直方向の中央に表示されます。

Dd297679.fig17-16(ja-jp,MSDN.10).gif

17.20 | ホットキーの表示

StringFormatのHotkeyPrefixプロパティは、DrawStringメソッドによるアンパサンド(&)の解釈方法を制御します。

▼StringFormatのプロパティ(抜粋)

プロパティ名 アクセス状態
HotkeyPrefix HotkeyPrefix Get/Set

DrawStringメソッドは、アンパサンドをどのように解釈しているのでしょうか。アンパサンドは、メニュー項目、ボタン、あるいはその他のコントロール内で使用されるテキスト内に組み込まれ、特別の意味を与えられています。この事実がわかれば、疑問への回答を簡単に用意できるでしょう。アンパサンドというのは、直後に続く文字に下線が引かれ、その文字がキーボードのショートカットとして機能することを明確にする特殊な文字です。

HotkeyPrefixプロパティは、System.Drawing.Text名前空間に定義されている次のHotkeyPrefix列挙体値のいずれかに設定できます。

▼HotkeyPrefix列挙体

メンバ 意味
None 0 「&File」 → 「&File」(既定)
Show 1 「&File」 → 「File」
Hide 2 「&File」 → 「File」

既定では、アンパサンドは特別に扱われず、単純にそのまま表示されます。Show値は、アンパサンドの表示を抑制し、後続する文字に強制的に下線を引きます。Hide値は、アンパサンドの表示を抑制しますが、後続する文字に下線を引くことはありません。

メニューやコントロール内でテキストを使用していない場合でも、このプロパティを使用し、DrawStringメソッドに渡すテキストブロック内に現れる特殊な文字や単語に下線を引くことができます。次のサンプルプログラムでは、このような目的でアンパサンドを使っています。


UnderlinedText.vb
'-----------------------------------------------
' UnderlinedText.vb (c) 2002 by Charles Petzold
'-----------------------------------------------
Imports System
Imports System.Drawing
Imports System.Drawing.Text
Imports System.Windows.Forms

Class UnderlinedText
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(New UnderlinedText())
    End Sub

    Sub New()
        Text = "Underlined Text Using HotkeyPrefix"
        Font = New Font("Times New Roman", 14)
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Dim str As String = "This is some &u&n&d&e&r&l&i&n&e&d text!"
        Dim strfmt As New StringFormat()
        strfmt.HotkeyPrefix = HotkeyPrefix.Show

        grfx.DrawString(str, Font, New SolidBrush(clr), 0, 0, strfmt)
    End Sub
End Class

このサンプルプログラムが表示する文字列は、それほど魅力的ではありませんが、次のような下線付きの文字列が表示されます。

Dd297679.fig17-17(ja-jp,MSDN.10).gif

DrawStringメソッドに渡すテキストブロック内の単語を斜体や太字で表示する方法が用意されていないのは残念です。

第9章で紹介したOwnerDrawMenuサンプルプログラム内では、HotkeyPrefixプロパティを本来の目的で応用しています。

17.21 | クリッピングとトリミング

RectangleFオブジェクトを受け取るDrawStringメソッドを使用するときには、テキストのワードラップを定義する右マージンだけではなく、表示されるテキストの全体の量を決定する下端マージンも定義するはずです。

テキストが長すぎて、矩形領域に入りきれない場合、いったいどのようなことが発生するのでしょうか。

まずは、既定の振る舞いを検討してみます。DrawStringメソッドの最後の引数としてStringFormatオブジェクトを指定していない場合はどうなるのでしょうか。矩形領域の高さが行間スペースの高さの整数倍となっている場合、テキスト行数は矩形領域内に収まると考えてよいでしょう。表示されるテキストの最終行は、矩形領域の幅に相当する文字数を含んでいるはずです。ここで注意してもらいたいのは、私は「文字数」という表現を使っていることです。単語という言葉を使っていません。この意味を具体的に説明するために、第3章で紹介したHuckleberryFinnプログラムの一部を改良したサンプルプログラムを用意しました。このHuckleberryFinnHalfHeightサンプルプログラムは、テキストの表示量を、クライアント領域の幅と高さの半分に制限しています。


HuckleberryFinnHalfHeight.vb
'----------------------------------------------------------
' HuckleberryFinnHalfHeight.vb (c) 2002 by Charles Petzold
'----------------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class HuckleberryFinnHalfHeight
    Inherits Form

    Shared Sub Main()
        Application.Run(New HuckleberryFinnHalfHeight())
    End Sub

    Sub New()
        Text = """The Adventures of Huckleberry Finn"""
        BackColor = SystemColors.Window
        ForeColor = SystemColors.WindowText
        ResizeRedraw = True
    End Sub

    Protected Overrides Sub OnPaint(ByVal pea As PaintEventArgs)
        Dim grfx As Graphics = pea.Graphics
        Dim cx As Integer = ClientSize.Width
        Dim cy As Integer = ClientSize.Height
        Dim pn As New Pen(ForeColor)
        Dim rectf As New RectangleF(0, 0, cx \ 2, cy \ 2)

        grfx.DrawString("You don't know about me, without you " & _
                        "have read a book by the name of ""The " & _
                        "Adventures of Tom Sawyer,"" but that " & _
                        "ain't no matter. That book was made by " & _
                        "Mr. Mark Twain, and he told the truth, " & _
                        "mainly. There was things which he " & _
                        "stretched, but mainly he told the truth. " & _
                        "That is nothing. I never seen anybody " & _
                        "but lied, one time or another, without " & _
                        "it was Aunt Polly, or the widow, or " & _
                        "maybe Mary. Aunt Polly" & ChrW(&H2014) & _
                        "Tom's Aunt Polly, she is" & ChrW(&H2014) & _
                        "and Mary, and the Widow Douglas, is all " & _
                        "told about in that book" & ChrW(&H2014) & _
                        "which is mostly a true book; with some " & _
                        "stretchers, as I said before.", _
                        Font, New SolidBrush(ForeColor), rectf)

        grfx.DrawLine(pn, 0, cy \ 2, cx \ 2, cy \ 2)
        grfx.DrawLine(pn, cx \ 2, 0, cx \ 2, cy \ 2)
    End Sub
End Class

このサンプルプログラムは、テキストが表示されている矩形領域を明確にするために、矩形領域を直線で描いています。

矩形領域がすべての段落を表示するための十分な大きさがないとき、クライアント領域に表示される最終行は、不完全な単語を含んでしまうことになります。この点もはっきり確認できるはずです(つまり、単語ではなく、表示できるまでの文字数を含んでいます)。

Dd297679.fig17-18(ja-jp,MSDN.10).gif

表示領域の高さを大きくしていくと、DrawStringメソッドはある時点で、テキスト行を追加表示するための十分な領域が確保されていると判断しています。この判断はすばやく行われています。DrawStringは、矩形領域が行間スペースの高さの25%以上に増えると、次のテキスト行を表示しています。最終行の一部は、矩形領域の内部に切り取られたような状態になります。

Dd297679.fig17-19(ja-jp,MSDN.10).gif

StringFormatクラスのTrimmingプロパティを使用すると、この既定の振る舞いを変更することができます。

▼StringFormatのプロパティ(抜粋)

プロパティ名 アクセス状態
Trimming StringTrimming Set/Get

Trimmingプロパティは、テキストの最終行の処理方法を規定します。つまり、RectangleFオブジェクトを受け取るDrawStringメソッドを呼び出しても、その矩形領域がすべてのテキストを表示できないとき、最終行の表示内容はこのプロパティの値により決まります。次のようなStringTrimming列挙体値が指定できます。

▼StringTrimming列挙体

メンバ 意味
None 0 下端マージンを設けない。
Character 1 文字で終了する。
Word 2 単語で終了する。
EllipsisCharacter 3 文字と省略記号(...)で終了する。
EllipsisWord 4 単語と省略記号(...)で終了する。
EllipsisPath 5 省略記号(...)と最新ディレクトリで終了する。

次に紹介するサンプルプログラムは、各種Trimmingプロパティ値を応用した例です。


TrimmingTheText.vb
'------------------------------------------------
' TrimmingTheText.vb (c) 2002 by Charles Petzold
'------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class TrimmingTheText
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(new TrimmingTheText())
    End Sub

    Sub New()
        Text = "Trimming the Text"
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Dim br As New SolidBrush(clr)
        Dim cyText As Single = Font.GetHeight(grfx)
        Dim cyRect As Single = cyText
        Dim rectf As New RectangleF(0, 0, cx, cyRect)
        Dim str As String = "Those who profess to favor freedom and " & _
                            "yet depreciate agitation. . .want " & _
                            "crops without plowing up the ground, " & _
                            "they want rain without thunder and " & _
                            "lightning. They want the ocean without " & _
                            "the awful roar of its many waters. " & _
                            ChrW(&H2014) & "Frederick Douglass"
        Dim strfmt As New StringFormat()

        strfmt.Trimming = StringTrimming.Character
        grfx.DrawString("Character: " & str, Font, br, rectf, strfmt)
        rectf.Offset(0, cyRect + cyText)

        strfmt.Trimming = StringTrimming.Word
        grfx.DrawString("Word: " & str, Font, br, rectf, strfmt)
        rectf.Offset(0, cyRect + cyText)

        strfmt.Trimming = StringTrimming.EllipsisCharacter
        grfx.DrawString("EllipsisCharacter: " & str, _
                        Font, br, rectf, strfmt)
        rectf.Offset(0, cyRect + cyText)

        strfmt.Trimming = StringTrimming.EllipsisWord
        grfx.DrawString("EllipsisWord: " & str, _
                        Font, br, rectf, strfmt)
        rectf.Offset(0, cyRect + cyText)

        strfmt.Trimming = StringTrimming.EllipsisPath
        grfx.DrawString("EllipsisPath: " & _
                        Environment.GetFolderPath _
                                (Environment.SpecialFolder.Personal), _
                        Font, br, rectf, strfmt)
        rectf.Offset(0, cyRect + cyText)

        strfmt.Trimming = StringTrimming.None
        grfx.DrawString("None: " & str, Font, br, rectf, strfmt)
    End Sub
End Class

このサンプルプログラムでは、1行のテキストを表示するための十分な高さを持つRectangleFオブジェクトを作成しています。6種類のStringTrimming値を変更することにより、このプログラムは同一テキストを表示しています。テキストはFrederick Douglassの引用文であり、StringTrimming.EllipsisPath以外のすべての値を使って表示されます。私はこのプログラムでは、EllipsisPath値の代わりに、Environment. GetFolderPathメソッドを呼び出して、My Documentsフォルダを取得しています。ウィンドウ幅を調整すれば、幅とテキストの関係を確認することができます。実行例を次に示します。

Dd297679.fig17-20(ja-jp,MSDN.10).gif

StringTrimmingのEllipsisCharacterとEllipsisWordメンバは、いずれも文字列の最後に省略記号(...)を表示させ、テキストを表示するための十分な領域が確保されていないことを明示しています。CharacterとEllipsisCharacterは、単語の一部(文字)を表示しています。

EllipsisPathメンバは、ファイルのパス名を表示するための専用メンバと考えてしまうとよいでしょう。省略記号は、テキストの中央に組み入れられ、パスの先頭と最後が明確になるようになっています。

Noneメンバは、一見すると、Wordと同じように見えますが、実際は違います。次の操作をすれば、このメンバがなぜプログラムの最後で使われているかがわかるはずです。


Dim cyRect As Single = cyText

このコードを次のように変更します。


Dim cyRect As Single = 1.5F * cyText

再コンパイルして実行してみてください。次のような画面が表示されるはずです。

Dd297679.fig17-21(ja-jp,MSDN.10).gif

DrawStringメソッドは、このように2行のテキスト行を表示しています。2行目のテキスト内容はほとんど見ることはできませんが、その行こそTrimmingプロパティの影響を受けている行なのです。

この時点で、StringFormatFlags列挙体のNoWrapフラグの意味を知りたくなったい人もいるでしょう。StringFormatオブジェクトの作成コードと1番目のDrawStringメソッド呼び出しの間に、次のようなコードを追加してみてください。


strfmt.FormatFlags = strfmt.FormatFlags Or StringFormatFlags.NoWrap

あるいは、次のようなコードでもよいでしょう。


Dim strfmt As New StringFormat(StringFormatFlags.NoWrap)

名称が示すように、NoWrapフラグはDrawStringの行折り返し機能を無効にします。実行画面を次に示します。

Dd297679.fig17-22(ja-jp,MSDN.10).gif

このように、テキストは矩形領域の右マージンで終了しています。ここではっきり見えないかもしれませんが(矩形領域の右マージンをクライアント領域幅より小さくすると、はっきり見えるのですが)、StringTrimming.Noneを有効にしていると、最後の文字の一部が右マージンで切り捨てられます。これは、これまでのところでは、文字の一部だけが表示されるという初めての経験のはずです。

それではここで、NoWrapフラグを削除するか、次のコードと差し替えてみてください。


strfmt.FormatFlags = strfmt.FormatFlags Or StringFormatFlags.NoClip

このフラグは、DrawStringメソッドに対して、表示するための矩形領域の外部にあるテキストの一部を切り取らないように指示します。このため、2行のテキスト行は、その一部が欠けることなくすべて表示されます(ただし、StringTrimming.Noneが有効なときは除きます)。

Dd297679.fig17-23(ja-jp,MSDN.10).gif

StringFormat.Noneの場合、この画面ではテキストブロック全体が表示されています。この列挙体値とNoClipフォーマットフラグの組み合わせ指定は、実は、矩形領域の下端の書式制御を否定するように作用します。

次のように、2つのフラグを有効にします。


strfmt.FormatFlags = strfmt.FormatFlags Or StringFormatFlags.NoClip
strfmt.FormatFlags = strfmt.FormatFlags Or StringFormatFlags.NoWrap

Noneを除くすべてのStringTrimming値において、NoWrapフラグだけを有効にした場合と同じ結果になります。StringTrimming.Noneの場合、テキストは折り返されることなく、右マージンからはみ出るようなこともありません。結果的には、DrawString呼び出し内で、RectangleFではなく、PointFを使用した場合と同じです。

矩形領域にテキストを表示するときには、切り取り(クリッピング)に注意する必要があります。矩形領域の高さを、行間スペース値の整数倍にしている場合、このクリッピング問題は発生しません。おそらく、これが最良のソリューションといえるでしょう。しかし、このような設定にしていない場合、NoClipフォーマットフラグを有効にし、クリッピングを防止するとよいでしょう。ただし、その場合、テキストの最終行の一部が、矩形領域の下端外部に表示される可能性もあります。いくつかの場面では(たとえば、矩形領域の高さがクライアント領域の高さとなっている場合など)、テキストの最終行はクリッピングされるはずです。理由は単純です。クライアント領域境界を越えるからです。このため、矩形領域を調整し、すべてのテキスト行が確実に表示されるようにするとよいでしょう。

既定コンストラクタを使ってStringFormatオブジェクトを作成している場合、次のようになります。


Dim strfmt As New StringFormat()

あるいは、StringFormatの共有プロパティを使って作成している場合、次のようになります。


Dim strfmt As StringFormat = StringFormat.GenericDefault

Trimmingプロパティは、StringTrimming.Characterに初期設定されます。共有プロパティを使用して作成しているときには、次のようになります。


Dim strfmt As StringFormat = StringFormat.GenericTypographic

Trimmingプロパティは、StringTrimming.Noneに初期設定され、NoClipフォーマットフラグも設定されます。

17.22 | タブの開始

タブストップは、DrawStringメソッドのUnicodeタブ文字Chr(9)やvbTabの解釈方法を制御します。DrawString呼び出し時にStringFormat引数を渡していない場合、既定のタブストップ(ポイント単位)は、フォントの大きさの4倍となっています。表現を変えれば、タブは4emということです。たとえば、9ポイントのフォントは、36ポイント(1/2インチ)ごとにタブストップを持ちます。18ポイントのフォントは、72ポイント(1インチ)ごとにタブストップを持ちます。さらに、36ポイントのフォントは、144ポイント(2インチ)ごとにタブストップを持ちます。タブストップの間隔は、DrawStringメソッドに渡されるPointFやRectangleF引数により指定されるテキスト開始位置から測定されます。

DrawStringメソッドにStringFormat引数を渡している場合、既定タブストップは存在せず、DrawStringメソッドはテキスト内のすべてのタブ文字を無視します。この場合、StringFormatのSetTabStopsメソッドを呼び出し、タブストップを設定する作業が必要です。StringFormatクラスは、現在のタブストップ設定を取得できるように、次のようなメソッドを用意しています。

▼StringFormatのメソッド(抜粋)

Sub SetTabStops(ByVal fFirstTab As Single, ByVal afTabs As Single()) Function GetTabStops(ByRef fFirstTab As Single) As Single()

タブストップは、ワールド座標単位で表現されます。後で解説しますが、タブストップはSingle値とSingle値配列の両方で指定されます。

SetTabStopsメソッドの一般的な使い方を説明する前に、2、3の単純な例を示しておきましょう。この例では、ページ単位がGraphicsUnit.Pointに設定されているものとします。

4インチ(288ポイント)のタブストップを1つ設定する必要がある場合、その数値をメソッドの第1の引数として指定し、配列に0を含めるようにします。次のようなコードが書けるでしょう。


strfmt.SetTabStops(288, New Single() { 0 })

配列引数にNothingを設定することはできません。ただし、次のようなコーディングは許されます。


strfmt.SetTabStops(0, New Single() { 288, 0 })

1インチ(72ポイント)と3インチ(216ポイント)という2つのタブストップを設定する必要がある場合には、次のようなコードを記述できます。


strfmt.SetTabStops(0, New Single() { 72, 144, 0 })

このコードを見るとわかるように、第2の配列要素は、72と216ポイント間の差を指定しています。私は今、SetTabStopsメソッド内で直接配列を作成するコードを紹介していまが、もちろん、メソッド外で配列を定義することもできます。

0.5インチ(36ポイント)のタブストップが必要な場合、次のように指定できます。


strfmt.SetTabStops(0, New Single() { 36 })

タブストップは、36、72、108、144ポイントなどになります。

ご存知のように、SetTabStopsメソッドは、タブストップを個別に設定することも、連続的に設定することもできます。これらの設定方法を組み合わせる場合、メソッドの使い方が複雑になります。一般的には、SetTabStopsメソッドは、次のような引数を受け取ると考えておくとよいでしょう。


strfmt.SetTabStops(S, New Single() { A, B, C, ..., N, R })

ここでは、RとSという文字を使っていますが、それぞれRepeating(繰り返し)とShift(シフト)を示しています。これらの値は、0や負の値を取ることができます。SetTabStopsメソッドは、テキストの開始点から次の位置にタブストップを設定します。


S + A
S + A + B
S + A + B + C
S + A + B + C + ... + N
S + A + B + C + ... + N + R

また、このメソッドは、R、2R、3Rなどの位置にタブストップを設定しますが、これらの繰り返しタブストップは、他のタブストップの中の最大値の後から開始されます。たとえば、次のようなコードが記述されているとします。


strfmt.SetTabStops(100, New Single() { 50, 75, 50, 100 })

このコードは、150、225、275、375、400、500、600などにタブストップを設定します。単位はワールド座標が使われます。

すべてのタブストップを明示的に定義したい場合には、最後の配列要素(R)を0に設定することができます。また、Sを0に設定することもできます。しかし、この初期値は、慎重に利用した方がよいと思います。たとえば、水平座標0から始まる4個のタブストップを持つ配列を次のように定義するとしましょう。


Dim afTabs() As Single = { 100, 150, 100, 50, 0 }

ここでは、最後の引数を0に設定していることに注意してください。この場合、繰り返しタブストップがないことになります。

水平座標0からテキストを表示するような場合は、次のように第1の引数に0という値を初期値として設定するとよいでしょう。


strfmt.SetTabStops(0, afTabs)

このコードは、タブストップを100、250、350、400単位に設定します。しかし、水平座標50からテキスト表示を開始するような要求変更が発生することもあります。しかも、同じ物理位置にタブストップを設定する条件が課された場合、どうなるのでしょう。この場合、SetTabStopsメソッドの第1の引数に-50を設定することができます。


strfmt.SetTabStops(-50, afTabs)

これにより設定されるタブストップは、50、200、300、350となりますが、開始点座標は50となります。このため、実際の位置は、100、250、350、400となり、要求変更の第2条件をクリアしています(つまり、タブの物理位置は以前と同じになります)。

それでは、これまでの学習内容を実際の場面に応用してみましょう。ここでは、多量のテキストをカラムを使って書式化します。この例で使用するテキストは、Edith Whartonの1920年の小説『The Age of Innocence』から引用しています。次に紹介するクラスは、読み取り専用のTextプロパティを持ち、小説の最初の5段落を返してきます。


AgeOfInnocence.vb
'----------------------------------------------------------------------
' AgeOfInnocence.vb (c) 2002 by Charles Petzold; text by Edith Wharton
'----------------------------------------------------------------------

Class AgeOfInnocence

    Shared ReadOnly Property Text() As String
        Get
            Return _
On a January evening of the early seventies, Christine Nilsson  & _
was singing in Faust at the Academy of Music in New York. & _
vbLf & _
vbTab & _
Though there was already talk of the erection, in remote  & _
metropolitan distances "above the Forties," of a new Opera  & _
House which should compete in costliness and splendour with  & _
those of the great European capitals, the world of fashion was  & _
still content to reassemble every winter in the shabby red and  & _
gold boxes of the sociable old Academy. Conservatives  & _
cherished it for being small and inconvenient, and thus keeping  & _
out the "new people" whom New York was beginning to dread and  & _
yet be drawn to and the sentimental clung to it for its historic  & _
associations, and the musical for its excellent acoustics,  & _
always so problematic a quality in halls built for the hearing  & _
of music. & _
vbLf & _
vbTab & _
It was Madame Nilsson's first appearance that winter, and what  & _
the daily press had already learned to describe as "an  & _
exceptionally brilliant audience" had gathered to hear her,  & _
transported through the slippery, snowy streets in private  & _
broughams, in the spacious family landau, or in the humbler but  & _
more convenient "Brown &c&o&u&p&e& & _  
." To come to the Opera in a Brown &c&o&u&p&e&  & 
 was almost as honourable a way of arriving as in one's own  & _
carriage and departure by the same means had the immense  & _
advantage of enabling one (with a playful allusion to democratic  & _
principles) to scramble into the first Brown conveyance in the  & _
line, instead of waiting till the cold-and-gin congested nose  & _
of one's own coachman gleamed under the portico of the Academy.  & _
It was one of the great livery-stableman's most masterly  & _
intuitions to have discovered that Americans want to get away  & _
from amusement even more quickly than they want to get to it. & _
vbLf & _
vbTab & _
When Newland Archer opened the door at the back of the club box  & _
the curtain had just gone up on the garden scene. There was no  & _
reason why the young man should not have come earlier, for he  & _
had dined at seven, alone with his mother and sister, and had  & _
lingered afterward over a cigar in the Gothic library with  & _
glazed black-walnut bookcases and finial-topped chairs which was  & _
the only room in the house where Mrs. Archer allowed smoking.  & _
But, in the first place, New York was a metropolis, and  & _
perfectly aware that in metropolises it was "not the thing" to  & _
arrive early at the opera and what was or was not "the thing"  & _
played a part as important in Newland Archer's New York as the  & _
inscrutable totem terrors that had ruled the destinies of his  & _
forefathers thousands of years ago. & _
vbLf & _
vbTab & _
The second reason for his delay was a personal one. He had  & _
dawdled over his cigar because he was at heart a dilettante, and  & _
thinking over a pleasure to come often gave him a subtler  & _
satisfaction than its realisation. This was especially the case  & _
when the pleasure was a delicate one, as his pleasures mostly  & _
were; and on this occasion the moment he looked forward to was  & _
so rare & exquisite in quality that & ChrW(&H2014) & "well, if " & _
he had timed his arrival in accord with the prima donna's  & _
stage-manager he could not have entered the Academy at a more  & _
significant moment than just as she was singing: "He loves me & _
ChrW(&H2014) & "he loves me not" & ChrW(&H2014) & "&h&e& " & _

&l&o&v&e&s& &m&e!" and sprinkling the falling daisy petals with  & _
notes as clear as dew. & _
vbLf
        End Get
    End Property
End Class

タブ文字を使って、第1段落を除く各段落の最初の行をインデントしています。テキスト内のいくつかの単語は、斜体表示されています。この表示では、既に紹介したアンパサンドテクニックを使っています(下線を引いているのではなく、斜体表示として応用します)。また、テキスト内には、emダッシュも使われています。

次のサンプルプログラムは、このテキストをカラム単位に整理したうえで表示します。個々のカラム表示は、DrawStringメソッドで実装されています。


TextColumns.vb
'--------------------------------------------
' TextColumns.vb (c) 2002 by Charles Petzold
'--------------------------------------------
Imports System
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Drawing.Text
Imports System.Windows.Forms

Class TextColumns
    Inherits PrintableForm

    Shared Shadows Sub Main()
        Application.Run(new TextColumns())
    End Sub

    Sub New()
        Text = "Edith Wharton's ""The Age of Innocence"""
        Font = new Font("Times New Roman", 10)
    End Sub

    Protected Overrides Sub DoPage(ByVal grfx As Graphics, _
            ByVal clr As Color, ByVal cx As Integer, ByVal cy As Integer)
        Dim br As New SolidBrush(clr)
        Dim x, iChars, iLines As Integer
        Dim str As String = AgeOfInnocence.Text
        Dim strfmt As New StringFormat()

        ' 変換用にポイント単位を設定する
        Dim aptf() As PointF = {New PointF(cx, cy)}
        grfx.TransformPoints(CoordinateSpace.Device, _
                             CoordinateSpace.Page, aptf)

        grfx.PageUnit = GraphicsUnit.Point
        grfx.TransformPoints(CoordinateSpace.Page, _
                             CoordinateSpace.Device, aptf)

        Dim fcx As Single = aptf(0).X
        Dim fcy As Single = aptf(0).Y

        ' StringFormatプロパティ、フラグ、タブの設定
        strfmt.HotkeyPrefix = HotkeyPrefix.Show
        strfmt.Trimming = StringTrimming.Word
        strfmt.FormatFlags = strfmt.FormatFlags Or StringFormatFlags.NoClip
        strfmt.SetTabStops(0, New Single() {18})

        ' テキストの表示
        For x = 0 To CInt(fcx) Step 156
            If (str.Length <= 0) Then Exit For

            Dim rectf As New RectangleF(x, 0, 144, _
                                        fcy - Font.GetHeight(grfx))
            grfx.DrawString(str, Font, br, rectf, strfmt)
            grfx.MeasureString(str, Font, rectf.Size, strfmt, _
                               iChars, iLines)
            str = str.Substring(iChars)
        Next x
    End Sub
End Class

このサンプルプログラムでは、StringFormatプロパティ、フラグおよびタブの設定に注意してください。HotkeyPrefix.Showを使い、下線が表示されるようにしています。また、StringTrimming.Wordを指定し、個々の矩形領域の下端にワードを表示するようにもしています。さらに、StringFormatFlags.NoClipを有効として、テキスト行が矩形領域の下端で切り取られないような配慮もしています。シングルタブ(1行目のインデントを決めます)を18ポイントに設定しています。Forループでは、個々のカラムを繰り返し表示しています。この繰り返し処理は、クライアント領域幅を超えるか、あるいは、すべてのテキストが表示されるまで継続しています。ループ内では、表示するための矩形領域の高さは、クライアント領域の高さからテキスト1行分の高さを差し引いた差として算出しています。DrawStringメソッドでは、その算出結果をそのまま使っています。MeasureStringメソッドは、DrawStringメソッドが表示したテキスト量を判断するために使われています。StringクラスのSubStringメソッドが使われていますが、このメソッドは、次のループ処理用の文字列を準備しています。

実行画面は次のとおりです。

Dd297679.fig17-24(ja-jp,MSDN.10).gif