最近Tategaki熱が再燃しています。大幅に機能を追加しました。
Nuget:
https://www.nuget.org/packages/Tategaki/
追加した機能
TategakiTextのプロパティ
追加した機能はReadmeのほうにも書いてありますが、TextBlockにある主要なプロパティを TategakiTextにも追加し実装しました。追加したプロパティは以下の通りです。
- TextWrapping
- TextDecorations
- LineHeight
- TextAlignment
- Padding
- LastForbiddenChars / HeadForbiddenChars / LastHangingChars
- EnableHalfWidthCharVertical
1番から5番まではTextBlockにもあるプロパティで、その挙動も極力TextBlockに似せているので特段使い方の説明はいらないと思います。
TextWrappingが実装されたことにより、今まで1行表示しかできなかったTategakiTextが複数行表示もできるようになりました。改行文字も認識しますので、下のデモソフトのように小説のような長い文章でもちゃんと折り返して正確に表示できます。TategakiMultilineは多数のTategakiTextをItemsPanelで並べて折り返しを実現していたので、それに比べればだいぶ動作が軽快になりました。
禁則文字 / ぶらさげ文字
TategakiMultilineには禁則文字(文末禁止文字 / 文頭禁止文字)が設定できました。TategakiTextでも同様の設定ができるようになったうえ、文末ぶらさげ文字も設定できるようになっています。文末に来た場合、はみ出し前提で下にぶらさげる文字ですね。
また、TextWrapping列挙型にはWrapとWrapOverflowという2種類のオプションがあります。
TextWrapping.Wrap
TextWrapping.WrapOverflow
長い一単語があって幅が入りきらなくなったとき、単語の途中で折り返すのがWrap、単語を右側にはみ出させるのがWrapOverflowです。もちろんTategakiTextもこの機能に対応しています。禁則文字の処理の一環として実装されています。
EnableHalfWidthCharVertical
半角の文字を縦書きにするかどうかのオプションです。
上に2つのテキストがありますが、左がこのオプションがOFF、右がONです。ちなみにフォントにvrt2が含まれている場合はこのオプションにかかわらず左側のスタイルになります。
フォント読み込み処理回り
従来はEnvironment.GetFolderPathメソッドを使ってシステムのフォントファイルを読み込んで縦書きが有効なフォントを抽出していました。
string FontDir = Environment.GetFolderPath(Environment.SpecialFolder.Fonts); var uris = Directory.GetFiles(FontDir, "*.ttf").Concat(Directory.GetFiles(FontDir, "*.otf")).Select(p => new Uri(p)) .Concat(Directory.GetFiles(FontDir, "*.ttc").SelectMany(p => { using(var fs = new FileStream(p, FileMode.Open, FileAccess.Read)) { return Enumerable.Range(0, TypefaceInfo.GetCollectionCount(fs)).Select(i => new UriBuilder("file", "", -1, p, "#" + i).Uri); } }) );
これはグリフレベルで描画をする際にどうしてもフォントのURIが必要だからです。ですが、いわゆる「游ゴシック」などのフォントファミリー名からURIを取得する手法がわからず、逆にURIからフォントファミリー名を取得してテーブルとして保持していました。
しかし、フォントはWindowsの特定のユーザーのみにインストールすることもでき、その場合はシステムフォルダ(C:\Windows\Fonts;GetFolderPathで取得できるフォルダ)にフォントファイルは入りません。ですので今までそのようなフォントは読み込めませんでした。
ユーザー用フォントのフォルダを足すのは簡単ですが、Windowsの仕様変更があればまたソフト側でも対応する必要が出てきます。ですので、やはり何かOSやフレームワークが提供する何らかの方法でフォントを取得したいですよね。
そしていろいろと調べていった結果、最終的に以下のような処理にたどり着きました。
var fonttable = new Dictionary<string, VerticalFontInfo>(); var namelist = new List<string>(); foreach(var ff in Fonts.SystemFontFamilies) { var tf = new Typeface(ff.Source); if(!tf.TryGetGlyphTypeface(out var gtf)) // GlyphTypefaceが取得できなければ用無し continue; int num = gtf.FontUri.Fragment == "" ? 0 : int.Parse(gtf.FontUri.Fragment.Replace("#", "")); var tfi = new TypefaceInfo(gtf.GetFontStream(), num); VerticalConverterType convtype = VerticalConverterType.None; if(tfi.GetVerticalGlyphConverter().Count > 0) convtype |= VerticalConverterType.Normal; if(tfi.GetAdvancedVerticalGlyphConverter().Count > 0) convtype |= VerticalConverterType.Advanced; if(convtype == VerticalConverterType.None) // 縦書きコンバーターが取得できなければ用無し continue; var vfi = new VerticalFontInfo(gtf, ff.Source, convtype); namelist.Add(vfi.OutstandingFamilyName); foreach(var name in vfi.FamilierFamilyNames.Select(p => p.familyname).Distinct()) fonttable[name] = vfi; } namelist.Sort();
まず、Fonts.SystemFontFamiliesでシステムに存在するフォントファミリー名を取得します。その後、そのフォントファミリー名をもとにTypefaceクラスをインスタンス化し、そこからGlyphTypefaceを取得することで、そのメンバからURIを取得することができるのです。
ただ、Fonts.SystemFontFamiliesで取得したフォントファミリー名とGlyphTypeface内にあるフォントファミリー名が違うことがあるようです。この辺のWindowsの挙動がほんとよくわからないですが、Tategakiではどのファミリー名でも目的のURIにたどり着けるように一生懸命キャッシュしています。
さて、ここまで作ったら、まあだいたいの用途は満たせそうなのでこんなもんで良いかな…。 TextTrimmingの実装とかをやっても良いかもしれませんが、本家TextBlockって実はHTMLみたいな結構強力な表現ができるみたいで、いろいろ実装していたらきりが無いですからね。まあ、複数フォントを混ぜたかったら複数TategakiTextを並べれば良いだけですし。
😮バージョン上がったのですね。TextWrappingなど便利そうですね。
返信削除Glyphsの名前が出てくる時点で、私は深入りしたいと思わないです。縦書きできれば十分です。カリグラフィーみたいな話になってくるときりがない気が。何か要望、問題が出てこれば方向性がありますが。MacはWindowsよりフォント処理が凝っているとか聞いたことあります。
昔のメモを調べてたら、こんなの出てきました。
https://learn.microsoft.com/ja-jp/typography/
glyphについては、何も知りませんが、この辺が基礎的な話になるのでしょうか。↓
https://learn.microsoft.com/ja-jp/windows/win32/directwrite/glyphs-and-glyph-runs
文字セットからフォントデータを引っ張ってくるイメージぐらいしかありません。フォントの中身まで詳しく知りません。