OpenTypeフォントにも望み通りの扁平・長体をかける

1.CreateFontIndirectではうまくいかない?

Windowsで、フォントに任意の扁平(長体)をかけて描画するには、CreateFontIndirect(もしくはCreateFont)Apiを使えばいい、はずだった。90年代のプログラミング本にはそうした記述があった記憶がある。が、現在、実際にはOpenTypeフォントや一部のTrueTypeフォントでは、うまくいかない。

OpenTypeフォントなら今時はDirectWriteを使うべきなのではという声もあるだろうが、20世紀からある由緒正しい(?)Windows Apiを使って何とかしてみよう、というお題。
なお、以下の記述は、Windows Apiの関連仕様をすべて正しく把握できている保証はなく、試行錯誤した経験則と推測によるもの。(突っ込み歓迎)

とりあえず試してみる。
基本的にはCreateFontIndirectに渡す適切なLOGFONT構造体を作るだけ。以下、扁平に必要なLOGFONT構造体を作る最低限のコード。

実際にVclアプリケーションで「MS ゴシック」と「游ゴシック」で試してみる。

フォント名以外、全く同一の指定をしているはずなのに、結果はこうなってしまう。

右側の「游ゴシック」の方は、明らかに扁平率が違う。
しかも、天地の描画位置も微妙にずれている。等間隔に線を描画してみると分かる。

2.TEXTMETRIC構造体にない3メンバ

さて、”おかしな扁平” “ずれた位置”になってしまうことに関係しそうなのは、TEXTMETRIC構造体になく、NEWTEXTMETRICEX構造体には存在する以下の3メンバ。
Microsoftの解説は英語しかないが、google翻訳で和訳。

メンバ 解説(Google翻訳)
ntmSizeEM フォントの正方形のサイズを指定します。 この値は概念上の単位(つまり、フォントが設計された単位)になります。
ntmCellHeight フォントの概念的な単位での高さを指定します。 この値は、ntmSizeEMメンバーの値と比較する必要があります。
ntmAvgWidth フォントの平均文字幅を概念上の単位で指定します。 この値は、ntmSizeEMメンバーの値と比較する必要があります。

分かったような分からないような説明だが、ともかく、このNEWTEXTMETRIC構造体の値は、昔ながらのApi EnumFontFamiliesExのコールバック関数EnumFontFamProcで取得できる。

コードは後回しにして、先の2フォントの値はこうなっている。

MS ゴシック 游ゴシック
ntmSizeEM 256 2048
ntmCellHeight 256 2636
ntmAvgWidth 128 1972

MSゴシックの場合はシンプルだ。ntmAvgWidthはntmSizeEMのちょうど半分。ntmSizeEMとntmCellHeightの値が全く同じ。
一方、游ゴシックはntmAvgWidthがntmSizeEMの半分より大きい。ntmCellHeightもtmSizeEMの値を上回る。

先ほどの描画結果とにらみ合わせてみると…。

以下のように推察される。
ntmAvgWidth……ntmSizeEMと同じサイズにしたい場合、この値をlfWidthに指定して初めて正方になる
ntmCellHeight……ntmSizeEMと同じサイズを指定した場合、描画時に、この値分の領域を上下に取る(つまりずれる)

3.3メンバを考慮した指定と描画

つまり、フォントに、望み通りの扁平(長体)をかけ、望み通りの位置に描画するには
(1)LOGFONTの指定時に、lfWidthにntmSizeEMとntmAvgWidthを考慮した値を指定する
(2)描画時の天地位置に、ntmSizeEMとntmCellHeightを考慮した値を指定する
ことが必要になる。

以下、サンプルコード。フォントごとにこれらの値を取得しておき、LOGFONT構造体作成時および描画時に値を調整する。必要な値はVclFormのコンストラクタで取得している。(丸め処理等は省略)

LOGFONT作成関数は以下のように書き換える

実際に描画するコードでは位置調整をする。

実行してみると、望み通りの扁平描画になる。

天地位置も正常。

あらゆるOpenTypeフォントでうまくいくのかどうかは不明だが、手持ちのOpenTypeフォントでは上手くいっている。
C++Builder10.2.3、Windows10で確認。

TDateの++演算子、欠落した日付の列挙

TDateには++演算子が使えることを今さら知った。値を一日ずつ進めてくれる。
比較演算子(<)もあるので、std::setやmapにも格納できる。

Vclアプリケーションでのテストコード。

出力結果:
2018/10/31
2018/11/01

もちろん閏月判定もやってくれる。

出力結果:
2015/02/28
2015/03/01
—————-
2016/02/28
2016/02/29
2016/03/01

前置と後置の評価タイミングも当然異なる。

出力結果:
postfix : equal
prefix : differ

もともと与えられた日付の一覧から、欠落した日付を列挙する方法を調べていて気付いたので、以下、そのサンプル。

出力結果:
2016/02/26
2016/02/28
2016/02/29
2016/03/04
2016/03/05
2016/03/08

なおTDateはTDateTimeのtypedefなので、TDateTimeでも同様に++演算子で1日ずつ値が進む。

出力結果:
2018/11/30 14:35:21
2018/12/01 14:35:21
2018/12/02 14:35:21

Vcl/FireMonkey共通。
C++Bulider10.2.3 / Windows10で確認。

ページの遷移にスライド効果を付ける[FireMonkey]

こういう感じのやつ。


FireMonkeyなのでWindows(32bit/64bit)でもAndoroidでも同じ処理。(iOSおよびiPhoneでも同様に動くはずだが、実機がないのでテストしていない)

FireMonkeyにはVclのPageControlはないので、TabControlを使う。
Vclとはかなり勝手が違う。ただしコードは1行も書く必要はない。

①FormにTabControlを貼り付け、右クリックメニュー[TTabItemの追加]からページを作成する。
slide%e5%8a%b9%e6%9e%9c_01%e3%83%bbtabcontrol01

②Tab表示を消す。TTabControlのオブジェクトインスペクタでTabPositionをNoneにする。
slide%e5%8a%b9%e6%9e%9c_02%e3%83%bbobjectinspector01

③Tab表示が消えた。
slide%e5%8a%b9%e6%9e%9c_03%e3%83%bbtabcontrol02

ここからが本題。

④フォームにActionListを貼り付けてダブルクリック。
メニューから[標準アクションの新規作成]。
slide%e5%8a%b9%e6%9e%9c_04%e3%83%bbactionlist01

⑤[標準アクションクラス]ウィンドウが出るので[タブ]の中の[NextTabItem]を選択
slide%e5%8a%b9%e6%9e%9c_05%e3%83%bbactionlist02

⑥さらに[標準アクションの新規作成]から今度は[PrevTabItem]を選択
slide%e5%8a%b9%e6%9e%9c_06%e3%83%bbactionlist03

⑦[NextTabItem]のオブジェクトインスペクタで[TabControl]プロパティに[TabControl1]を選ぶ。[PrevTabItem]も同様。ここでTransitionプロパティの値が[Slide]になっていることを確認する。ここがSlide効果の指定となる。
slide%e5%8a%b9%e6%9e%9c_07%e3%83%bbactionlist04

⑧TabItem1/TabItem2にそれぞれButtonを貼り付け、Button1のオブジェクトインスペクタで、[Action]にNextTabItemを選ぶ。
slide%e5%8a%b9%e6%9e%9c_08%e3%83%bbactiontobutton01

⑨Button1のキャプション(Textプロパティ)が自動的に[Next]に変わる。
slide%e5%8a%b9%e6%9e%9c_09%e3%83%bbactiontobutton01_2

⑩TabItem2にうつり、Button2の[Action]にこんどは[PrevTabItem]を選ぶ。
slide%e5%8a%b9%e6%9e%9c_10%e3%83%bbactiontobutton02_2

⑪Button2のキャプション(Textプロパティ)が自動的に[Prev]に変わる。
slide%e5%8a%b9%e6%9e%9c_11%e3%83%bbactiontobutton02_2

以上で終了。

各Actionの[Transition]プロパティのSlide/None選択だけで、ページ遷移時のSlide効果の有無を切り替えることが出来る。

なお、タブの標準アクションにはTChangeTabActionがあり、3ページ以上有る場合、ページ移動先の指定が可能。

C++Builder Berlin Update1で作成、Windows7(64bit)/Android4.4.2で動作確認。

ディレクトリ・ファイル操作の新旧方式

C++Bulider(RadStudio)のディレクトリやファイル操作に関しては、昔ながらのSystem.SysUtils配下の方法と、新しいSystem.IOUtils配下の方法がある。
まったく同一ではないが、基本的な操作は、どちらも似たようなことが出来る。

どちらでもいいといえば良いのだが、クロスプラットフォームを前提に開発するなら、新しいIOUtils配下にしかない関数群を使った方が便利なことが多いと思う。

どうせなら、Windows単体の開発でも、ふだんから新方式を使って慣れておいた方がいいというのが個人的な考え。

主な操作のそれぞれのやり方をまとめてみた。
(引数のpathはフルパスのディレクトリ名またはファイル名)

以下、VclとFiremonkey共通。

使い方
Includeファイル名
SysUtils System.SysUtils.hpp
IOUtils System.IOUtils.hpp
ファイル名からドライブ・ディレクトリ部分を得る
SysUtils ExtractFileDir( path )
IOUtils TPath::GetDirectoryName( path )
ディレクトリ名とファイル名からフルパスのファイル名を作る
SysUtils IncludeTrailingPathDelimiter( path )+filename
IOUtils TPath::Combine(path,filename)
ディレクトリの存在確認
SysUtils DirectoryExists(path)
IOUtils TDirectory::Exists(path)
ファイルの存在有無
SysUtils FileExists(path)
IOUtils TFile::Exists(path)
ディレクトリの作成
SysUtils CreateDir(path)
IOUtils TDirectory::CreateDirectory(path)
空のディレクトリを削除
SysUtils RemoveDir(path)
IOUtils TDirectory::Delete(path)
ディレクトリを中身ごと削除
SysUtils (自前で実装の必要あり)
IOUtils TDirectory::Delete(path,true)
ファイルを削除
SysUtils DeleteFile(path)
IOUtils TFile::Delete(path)

C++Builder Berlin Update2 / Windows7で確認。

TRectの便利関数

どのバージョンからか把握していないが、TRectにはいろいろな関数(やコンストラクタ)が実装されている。
Vclの一昔前のサンプルだと、こうした関数を使わない(ちょっと面倒くさい)処理が紹介されていたりする。
というか自分自身も昔の名残で、ついだらだらとコードを書きがちだったり。

Helpの一覧だと、実装が書いてあるだけで(RadStudio10.2 Tokypリリース直後時点)、まあ読めば分かるといえばそうなのだが、忘れがちなので一覧にしてみた。

System配下なので、Vcl・FireMonkey共通。(Delphiでも同じように使えるはず。)
以下、RはTRect、PtはTPointの変数。

動作 実例
PointからRectを作成 R = TRect(Pt1,Pt2)
Rectの値を正しくする R.Normalize()
位置をずらす R.Offset(X,Y)
R.Offset(Pt)
Rectの始点を指定する R.SetLocation(X,Y)
R.SetLocation(Pt)
Rectの始点を得る Pt = R.GetLocation()
Rectに点が含まれるか bool b = R.Intersects(Pt)
他のRectと重なっているか bool b = R1.Overlaps(R2)
他のRectを完全に含むか bool b = R1.Contains(R2)
全方向に大きく(小さく)する R.Inflate(X,Y)
R.Inflate(l,t,r,b)
中心点を得る Pt = R.CenterPoint()

FireMonkeyのイベント等で使われるTRectF/TPointFでもおおむね同様の関数が使える。(一部名前が違ったりするようだが、精査していない)

ScrollBoxのMouseWheelイベントをFormMouseWheelで捕まえる[VCL]

ScrollBoxのMouseWheelが捕まえられないときは、FormのMouseWheelイベントで捕まえる方法がある。

以上は、ScrollBoxがダイレクトにForm1にのっかっている時の例。
Form上のPanelの上にScrollBoxを配置しているときなどはもう一ひねり必要になる。

Unicodeのコード番号からStringを取得する

VclとFireMonkey共通。

“80CC”等、コードポイント(番号)の文字列をStringに変換するには、基本的には以下でOK。

ただし実際にはサロゲートペア用に割り当てられた番号(D800~DFFF)に配慮する必要がある。Androidで、サロゲートペア用の番号を指定するとアプリが落ちる場合もある(Android4.4.2で確認)
そこに配慮した関数。

サロゲートペア文字も取得するには、もう一手間が必要。
サロゲートペア用の番号は”20B9F”など5桁になる。
少し汎用にstd::wstringを使った関数。(注:C++Builderはwchar_tが2byte)

(注:C++Builder Berlin Update2では、Androidではwstring版の”wcstol”がバグっているようなので、さらに一手間かけてstring版の”strtol”を使う必要がある)

念のため、Stringに変換するには以下のように呼び出す。

(なおC++Builder BerlinではStringはUnicodeStringのtypedef)

C++Builder Berlin Update2 / Windows7(Sp1)で確認。

文字列に任意の角度で斜体をかける[VCL]

C++Builderで、文字に任意の角度で斜体をかけて描画する方法。
基本的に文字も点の集まりなので、その点の位置をずらして変形する。
具体的には、
 ①文字をパスでImage等のCanvasに描く
 ②パスから点の集合を取得する
 ③点の位置をずらした新たな点集合を作成
 ④変形用の点集合でCanvasに描く
という手順になる。

Vclにはパスを扱うクラスはないので、WindowsAPIを直接使うことになる。
以下、サンプル。FormにTrackBarとImageを置いた例。
(boost::shared_arrayを使っているが、生ポインタをnewしてももちろん動く)

こんな感じで変形する。

点の情報をずらすだけなので、さまざまな形に変形可能。
たとえば文字列に楕円が食い込んだように変形させる関数のサンプル。

こんな感じで変形する。

C++Builder Berlin Update2 / Windows7(Sp1)で確認。

続・Compositeパターンをshared_ptrで実装する…Clone/Assignの追加

Compositeパターンをshared_ptrで実装するの続き。

DeepCopyを作る”Clone”メソッドや、他のComponentから内容をコピーする”Assign”メソッドもあると便利。

Compositeもメンバ変数を保持することも想定して実装してみる。
もっとスマートなやり方もありそうだが、とりあえず、Compositeとその子クラスに非公開のデフォルトコンストラクタを実装する方式で。
(前回同様、c++11の機能を使っているが、c++11以前でもboost利用で同様の実装が可能。また行数省略のためすべてheaderに実装している。)

1.Componentクラス
Clone/Assign用の純粋仮想関数を追加する。

2.Compositeクラス
Clone/Assignメソッドを実装する。
Compositeの派生クラス用の純粋仮想関数doClone/doAssignも追加。
デフォルトConstructorはprotectedとする。
メンバ変数”name_”も追加、

3.ConcreteCompositeクラス
doClone/doAssignメソッドを実装する。非公開のコンストラクタも追加。
doCloneは非公開のデフォルトコンストラクタで作成したshared_ptrを返すだけ。

(不正なAssignの対応は何もしていないが、本来は例外を投げるなどのエラー処理が必要かも)

4.テストコード
適当に親子関係を作成した後、CloneとAssignを実行する。

実行結果
————————
name:c1/A: val == 3
 name:c2/B: str == child
  name:c3/B: str == child of child
   name:c4/A: val == 107
 name:c5/B: str == other child
–Clone–
name:c1/A: val == 3
 name:c2/B: str == child
  name:c3/B: str == child of child
   name:c4/A: val == 107
 name:c5/B: str == other child
–Before Assign–
name:assigned_compo/A: val == 0
–After Assign–
name:c1/A: val == 3
 name:c2/B: str == child
  name:c3/B: str == child of child
   name:c4/A: val == 107
 name:c5/B: str == other child
————————

ConcreteCompositeのメンバを追加・変更した場合、doAssignメソッドのみを書き換えればOK。

Compositeパターンをshared_ptrで実装する

かつて情報をネットで探したときに、断片的な情報はあるものの、分かりやすいサンプルが見付からなかったのでメモ。
Compositeパターンをshared_ptrで実装するのは、単にポインタをshared_ptrに置き換えるだけだと、循環参照が発生する。

ポイントは2つ。
 1.親(Parent)のポインタはshared_ptrでなくweak_ptrに置き換える
 2.shared_from_thisを使う

以下のコードはc++11以降のstl版だが、c++11以前でもboostで同様の実装が可能。必要なヘッダをincludeすれば”std::”を”boost::”に置き換えるだけでOK。
(説明を短くするために全てheaderに入れている)

まずComponentクラス。
メンバ”parent_”をweak_ptrで保持する。

(Executeは階層構造を出力するだけのテスト用Method)

つづいてCompositeクラス。
継承元に”std::enable_shared_from_this”を指定するのがポイント。
また”Add”で親をセットする際、”shared_from_this()”を使う。


CompositeのConcreteクラスを2つ作ってみる。ひとつはstd::string, もう一つはintをメンバに持つことにする。

実行してみる。適当に親子関係を作り、出力。

出力結果。
—————————————-
A: val == 3
 B: str == child
  B: str == child of child
   A: val == 107
 B: str == other child
—————————————-

生ポインタを使うのと比べ、解放処理が不要で便利。