fontTools のペンを使ってグリフのアウトラインを取得する
先日 Twitter で、「グリフのアウトラインの座標列を取りたいんだけど」「それ pen protocol でできるよ!」というやりとりをしました。この記事では、pen protocol に対応したペンでアウトラインを取得する方法について、具体的に解説してみます。
フォントとかについてベジエのアウトラインの法線方向に制御点を移動することでアウトラインを太らせたり細らせたりする処理を思ひついたんだけど、テスト環境構築するのが面倒。フォントファイルから制御点の座標列を取り出し、それを描画する処理ができないとだめ感。なにかいいのあるかな…。
— にせねこ (@nixeneko) 2017年11月8日
目的はもう果たせたようですが、グリフのアウトラインを Python で読み書き加工するのであれば、(segment) pen protocol https://t.co/kR5gvCz5kY や point pen protocol https://t.co/JvIUdqkEfu が標準的です。慣れるまでちょっと癖はあるものの、いろいろと応用が利きます。
— mashabow (@mashabow) 2017年11月8日
pen protocol とペン
さて、そもそも pen protocol とは何でしょう? 今回はプロトコルの詳細には触れず、概観だけ説明します。
フォント界隈では Python が共通言語になっています。しかし、グリフを表すためのオブジェクトは、フォントエディタ(Glyphs, RoboFont, FontLab, ...)やライブラリ(fontTools, ufoLib, defcon, fontParts, ...)ごとにそれぞれ独自に定義されています。ざっくり言ってしまうと、「どんなグリフオブジェクトであれ、共通のインターフェイスでアウトラインを読み書きできると楽だよね」「SVG や PDF やいろんな描画 API にも対応したいよね」というのが pen protocol の発想です。pen protocol によってグリフからアウトラインを得たり、アウトラインを描いたりするためのオブジェクトをペン(pen)と呼びます。
pen protocol が広まった結果、現在までにいろいろなペンが実装されてきました。以下にいくつか例を挙げますが、これ以外にも存在しています。
なお、pen protocol には segment pen protocol と point pen protocol の2系統があり、前者に対応したペンは HogePen
、後者は HogePointPen
のようなクラス名になっているのが通例です。この記事では、前者 segment pen protocol のペンをとり上げます*1。
フォントを読み込んでグリフを準備する
以下、環境は Python 3.6.3, fontTools 3.20.1 です。fontTools は $ pip install fonttools
でインストールできます。
ペンのことは一旦あと回しにして、まずはフォントファイルを読み込みます。今回は例として、源ノ角ゴシック Regular SourceHanSans-Regular.otf
を使いました。
from fontTools.ttLib import TTFont font = TTFont('SourceHanSans-Regular.otf')
次に、glyphSet と呼ばれる辞書様オブジェクトと、cmap(文字コードとグリフ名の対応)を取得しておきます。後者に関しては、従来 cmap テーブル font['cmap']
からサブテーブルを選んで辿っていく必要がありましたが、最近追加されたお手軽便利メソッド getBestCmap()
でいい感じに取得できます。
glyph_set = font.getGlyphSet() # {グリフ名: グリフ} っぽいオブジェクト cmap = font.getBestCmap() # {Unicode: グリフ名}
これらを使って、文字 char
に対応したグリフオブジェクトを返す関数を作ります。
def get_glyph(glyph_set, cmap, char): glyph_name = cmap[ord(char)] return glyph_set[glyph_name]
例として、文字 "L" に対応するグリフオブジェクトを、 L
としておきましょう。
L = get_glyph(glyph_set, cmap, 'L')
これでグリフの準備はできました。
RecordingPen
でアウトラインの内容を得る
さて、アウトラインの内容を取得するためには、fontTools に同梱されている RecordingPen
というペンを使います。まずはペンのインスタンスを作成します。
from fontTools.pens.recordingPen import RecordingPen recording_pen = RecordingPen()
次に、このペンをグリフ上で動かします。pen protocol に対応したグリフオブジェクトは draw()
メソッドを持っていますので、これにペンを渡して実行します。先ほどの "L"
のグリフ L
を使ってみましょう。
L.draw(recording_pen)
ペンで "draw" と言われると、どこかに描く・書き込むのかと思ってしまいがちですが、ここではグリフのアウトラインを「なぞる」行為のことだと捉えてください。この RecordingPen
では value
属性になぞった結果が入っていますので、見てみましょう。
print(recording_pen.value)
[('moveTo', ((100, 0),)), ('lineTo', ((513, 0),)), ('lineTo', ((513, 79),)), ('lineTo', ((193, 79),)), ('lineTo', ((193, 733),)), ('lineTo', ((100, 733),)), ('closePath', ())]
目的どおりアウトラインの内容が出てきました。"L" の左下の点からスタートし、反時計回りにパスが構成されているのが分かります。
もうひとつ、"い" のグリフで試してみるとこうなります。
い = get_glyph(glyph_set, cmap, 'い') recording_pen = RecordingPen() い.draw(recording_pen) print(recording_pen.value)
[('moveTo', ((226, 696),)), ('lineTo', ((130, 698),)), ('curveTo', ((135, 674), (136, 633), (136, 610))), ('curveTo', ((136, 552), (137, 432), (147, 346))), ('curveTo', ((174, 89), (264, -4), (357, -4))), ('curveTo', ((425, -4), (486, 53), (545, 221))), ('lineTo', ((482, 293),)), ('curveTo', ((456, 193), (410, 91), (359, 91))), ('curveTo', ((289, 91), (241, 200), (225, 366))), ('curveTo', ((218, 447), (217, 538), (218, 600))), ('curveTo', ((219, 626), (222, 672), (226, 696))), ('closePath', ()), ('moveTo', ((742, 669),)), ('lineTo', ((664, 642),)), ('curveTo', ((758, 526), (818, 330), (835, 152))), ('lineTo', ((916, 184),)), ('curveTo', ((902, 351), (831, 554), (742, 669))), ('closePath', ())]
曲線を中心とした、2つのパスで構成されています。
SVGPathPen
を利用してSVGを作成する
今度は応用として、SVGPathPen
というペンを使ってみましょう。このペンは、SVGのパスデータ文字列を組み立ててくれます。SVGPathPen
のコンストラクタは引数に glyphSet をとりますので、最初の方で用意した glyph_set
を渡します。
from fontTools.pens.svgPathPen import SVGPathPen svg_path_pen = SVGPathPen(glyph_set)
グリフ L
をなぞった後、getCommands()
メソッドでパスデータ文字列を取得します。
L.draw(svg_path_pen)
print(svg_path_pen.getCommands())
M100 0H513V79H193V733H100Z
パスデータ文字列が表示されました。左下の点 100 0
からスタートし、水平線 H
と垂直線 V
でパスが構成されています。
以下のようにガワを手書きして、独立したSVGファイルを作ります。
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"> <path d="M100 0H513V79H193V733H100Z"/> </svg>
このSVGファイルをブラウザで表示してみると、こんな感じです。
一応「L」のグリフが表示できました。が、上下逆さです。OpenType の座標系では y 軸が上方向に延びていますが、SVG では下方向に延びているため、そのままだと上下がひっくり返ってしまいます。transform
属性で上下を逆にし、viewBox
属性も調整しましょう。
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -1000 1000 1000"> <path d="M100 0H513V79H193V733H100Z" transform="scale(1, -1)"/> </svg>
これで正立しました。
最後に、もうちょっといい感じのSVGファイルを生成する関数を定義してみます。
from textwrap import dedent def save_as_svg(font, char, output_path): '''TTFont オブジェクトを受け取り、指定した文字のグリフを SVG として保存する''' glyph_set = font.getGlyphSet() cmap = font.getBestCmap() # グリフのアウトラインを SVGPathPen でなぞる glyph = get_glyph(glyph_set, cmap, char) svg_path_pen = SVGPathPen(glyph_set) glyph.draw(svg_path_pen) # メトリクスを取得 ascender = font['OS/2'].sTypoAscender descender = font['OS/2'].sTypoDescender width = glyph.width height = ascender - descender content = dedent(f'''\ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 {-ascender} {width} {height}"> <g transform="scale(1, -1)"> <!-- ボディの枠 --> <rect x="0" y="{descender}" width="{width}" height="{height}" stroke="cyan" fill="none"/> <!-- グリフ座標系の原点 --> <circle cx="0" cy="0" r="5" fill="blue"/> <!-- グリフのアウトライン --> <path d="{svg_path_pen.getCommands()}"/> </g> </svg> ''') with open(output_path, 'w') as f: f.write(content)
"L" と "い" で実行してみます。
save_as_svg(font, 'L', 'L.svg') save_as_svg(font, 'い', 'い.svg')
こんな感じの SVG ファイルができました。めでたしめでたし。
おわりに
この記事では pen protocol の概要を説明し、ペンを使ってアウトラインを取得したり、SVG として表示する方法について見てきました。一方、今回触れなかった話題としては、
- pen protocol の詳細とペンの定義方法
- グリフへの書き込み
- ペンを利用したアウトラインの加工
- さまざまなペンの紹介
などがあります。これらに関しては、またいずれ気が向いたときに記事を書くかもしれません。
なお、今回のファイルは以下のリポジトリにまとめておきました。どうぞご利用ください。
*1:若干紛らわしいのですが、segment pen protocol は単に pen protocol と呼ばれることがあります。
TTXによるフォントのチラ見をちょっといい感じにする設定
仕事場/プライベート問わずに大活躍の fontTools ですが、その中でも頻出なのが「フォントファイルを TTX でダンプして中身をチラ見したい」という場面です。
$ ttx -o out.ttx [オプション] font.otf $ less out.ttx
つい最近まで、上のようにしてごく普通に中身を見ていたのですが、何度もやっているといい加減めんどくさくなってきます。そこで、.zshrc
(適宜 .bashrc
などに読み替えてください)に簡単なシェル関数を定義してみました。
tl() { ttx -q -o - $* | source-highlight -s xml -f esc | less }
関数名はなんでもいいのですが、ttx
で less
なので短く tl
にしておきました。途中の source-highlight
は GNU Source-highlight というシンタックスハイライトの定番ツールです。こうしておけば、
$ tl [オプション] font.otf
とするだけでちょっといい感じに中身がチラ見できるようになります。べんり。
OpenType の仕様策定の議論をのぞくには
OpenType は1997年に Microsoft と Adobe が策定したフォントフォーマットで、現在のデファクトスタンダードになっています*1。仕様は Microsoft のサイトで公開されており、興味があればだれでもすぐに読むことができます。
この OpenType の仕様はときたま改定されており、今のところは2015年3月に発表された v1.7 が最新バーションです。
ところで、この仕様の改定はどこで議論されているんでしょう? UCS/Unicode や各種 Web 標準などは策定プロセスがオープンになっていますが、OpenType の仕様はどこで議論が進んでいるのか、ぱっと見ただけではよくわかりません。日本語の情報もほとんど見つかりません。というわけで、この記事でその一端を紹介したいと思います。
OpenType メーリングリスト
実は、Microsoft のサイトにこのメーリングリスト(ML)の案内がちゃんと載っています。
An e-mail based discussion forum has been set up specifically to help people working on the development of OpenType fonts and associated technologies.
- To subscribe to the OpenType Mailing List, send e-mail to: subscribe-opentype@indx.co.uk
- To remove yourself from this mailing list, send e-mail to: unsubscribe-opentype@indx.co.uk
載っているのですが、indx.co.uk
という馴染みのないドメインだったり、登録前に ML のアーカイブが見れなかったり、ページのフッタに This page was last updated 9 August 2004.
と書いてあったりと、登録に躊躇してしまう要素が満載です。
が、これでも現役でちゃんと生きている公式 ML です。to help people working on the development of OpenType fonts and associated technologies
と書かれていてスコープが少しぼんやりとしていますが、OpenType の仕様に関する話題が現在中心になっています。
この ML へ登録するためには、subscribe-opentype@indx.co.uk にメールを送ります*2。このとき空メールではだめで、メールの本文が必要です。自己紹介や「登録したいんだけど」みたいなことを1, 2文書いておけば大丈夫なはずです。ML 管理者の承認が降りれば、数日後に通知のメールが来ますので、これで登録完了です。過去のアーカイブは、ML 登録完了後にこのページからたどれます。ただしシステムが古いようで、使い勝手は残念な感じです。
なお、この ML から配信されるメールは、件名の頭に [OpenType]
がついています。
mpeg-OTspec メーリングリスト
世の中には ISO/IEC 14496-22 Open Font Format (OFF) という規格があります。といっても OpenType と中身は同じ…というか、OpenType をそのまま公的な国際規格にしたのがこの OFF です。デジュール標準というやつです。ISO が規格票を無料で公開しており、以下のページからPDFのダウンロードが可能です。ちなみに、ISO/IEC 14496-22:2015 が OpenType v1.7 に対応します。
そして、この規格について議論するための ML が米 Yahoo! Groups に用意されています。これが mpeg-OTspec ML です。
こちらの ML は mpeg-otspec-subscribe@yahoogroups.com に空メールを送信するだけですぐに参加できるので、OpenType ML と比べるととっつきやすいかもしれません。過去のアーカイブや添付ファイルの閲覧には米 Yahoo! のアカウントが必要になりますので、アカウントを持っていなければ作っておきましょう。
なお、この ML から配信されるメールは、件名の頭に [mpeg-OTspec]
がついています。
ちなみに、mpeg-OTspec ML の近年の流量は以下のとおりで、話題があるときとないときでムラがあります。これは OpenType ML の方も同様です。
また、両 ML 間では転送やクロスポストが多く、両方とも登録していると若干カオスです。
おまけ
最後におまけとして、以下のリポジトリを紹介しておきます。位置づけがまだよくわかりませんが、OpenType レイアウトに関する追加仕様のドラフトのようです。
二号明朝活字書体見本(明治26年)
TL;DR
東京築地活版製造所『二号明朝活字書体見本』(明治26年/1893年)の画像を Flickr で公開しました。
PDF 版がほしい方は下のページから。
いきさつ
先月末、Twitter でこんなやりとりをしていました。
手持ちの資料をネットに目録作って、私設図書館みたいな感じで参照したい人に情報を渡せる様にするといいのかもしれない。
— にせねこ (@nixeneko) November 28, 2015
手持ちの資料、紙モノまでは整理できてないけど、本とか冊子レベルのものならここに登録してる ⁑ しろもじライブラリ - メディアマーカー https://t.co/Pe8M3kdVOk
— mashabow (@mashabow) November 28, 2015
@mashabow うわっ、1893明26の「二号明朝総数見本 全」って、小宮山大明神の明25総数見本と同じ構成か、実はその1年で違ってたりするのか、激しく気になるんですが!!!
— UA (3.1) ⿴囗図デコ大好き (@uakira2) November 28, 2015
@uakira2 なんでしたらお貸ししますよ(そしてあわよくば撮影をお願いして…
— mashabow (@mashabow) November 28, 2015
@mashabow 撮影ご依頼、喜んで!!
— UA (3.1) ⿴囗図デコ大好き (@uakira2) November 28, 2015
@uakira2 どうもありがとうございます!(ちなみにわたしのTwitterのヘッダ画像はその見本帳の一部です
— mashabow (@mashabow) November 28, 2015
この『二号明朝活字書体見本 全』というのは、2年半ほど前にたまたま運良く入手した活字見本帳です。自分の本棚に古い見本帳があるというのは、もちろん飛び上がるほど嬉しいわけですが、それをそのまましまっておくのもどうももったいない。どうせなら、文字に興味を持ったいろいろな人に見てもらいたい*1。……などということを以前から考えていました。
そんな中でこのやりとりがあり、チャンスだと思って(ずうずうしくも)撮影をお願いしてみました。この内田(@uakira2)さんは以前、築地五号の見本帳のスキャンや、『光をかかぐる人々』の撮影をされています。ありがたいことに、今回快く撮影を引き受けてくださり、こうして『二号明朝活字書体見本 全』の画像を公開することができました。下の Filckr アルバムで全ページ閲覧できますので、どうぞご覧ください。表紙を含めて画像は88枚あります。
いつものように PDF 版も用意してあります。
見本帳概観
この見本帳の8割以上は、漢字のページで占められています。字種数はまだ数えていませんが、総数見本帳だけあってさまざまな字種が載っています。その分、整ったデザインの活字(頻出する漢字)から、その場しのぎっぽいバランス悪い活字(あまり出てこない漢字)まで、クオリティもさまざまです。
部首順に並んだ漢字が終わると、次は分合活字(この見本帳では「分合文字」とよぶ)のページに移ります。横幅が1/3, 2/3の2種類の活字があり、この両者を偏旁として組み合わせ、漢字1字を組み立てるようになっています。
その次はかな類が続きます。カタカナ・ひらがなともに、非常に洗練された細身のデザインです。上のツイートにあるように、自分の Twitter のヘッダ画像にはこのひらがな部分の写真を使っていますが、プロフィールページを開くたびについつい見とれてしまいます。
あとは記号類が少し並んで終わりです。全84ページ。奥付は以下のようになっています。
明治廿六年六月六日印刷
明治廿六年六月十日出版日本東京市京橋區築地二丁目拾七番地
印刷者 曲田 成
印刷所 東京築地活版製造所
というわけで、どうぞご活用ください。
*1:というかそもそも自分自身、ほかの人が持っている見本帳をいろいろ見たくてしょうがなかった
同型類字集 明朝体
前回の記事「同型類字集 等線体」のつづきです。
『同型類字集 等線体』の方は昭和17年(1942年)12月発行でしたが、こちらの『同型類字集 明朝体』は3ヶ月後の昭和18年(1943年)3月発行となっています。等線体が明朝体になっている以外は、まったく同じ体裁・内容のようです。
上の画像をクリックすると、Flickr でファイルが閲覧できます。例によって PDF にまとめたものも用意しましたので、必要な方は下のページからどうぞ。
例示されている明朝体を見てみると、やや直線的で整理された感じの雰囲気になっています。字体の省略や簡略化も見られますが、この処理は等線体のものと完全に一致しているようで、字体の整理・統一への意識が読み取れます。
他の明朝体と比べて特徴的なのは、撥ねの処理でしょうか。上の画像でわかるように、「力」や「巾」の撥ねが省略され、終筆を止めています。その一方で、「刂」や「寸」の撥ねはそのままの形で残っており、ちょっと不統一な感じもします。この撥ねる・撥ねないの間に、なにか法則性はあるんでしょうか…? わかる方いましたらぜひ教えてください。
さて、上の画像の左ページは表紙裏にあたる箇所ですが、ここにはこんな注意書きが書かれています。
一、一般字典ノ例ニ拠ルコトナク結体又ハ点画ヲ同シウスルモノニ分類編纂シ以テ練習ニ便ナラシム
二、本所載以外ノ文字ヲ必要トスル場合ハ末尾ニ添付シアル扁旁部首ニヨリ所要ノ扁旁冠脚等ニ供シ得ル文字ヲ選出シ適宜組成スルモノトス
煩字及略字ハ別ニ示ス備考
本類字集ノ字画ハ普通大ノ註記ニ適用スル為メ定メタルモノニシテ字高大ナル註記ニ在リテハ字典ヲ参照シ点画ヲ適宜粉飾スルコトヲ得
といっても、個人的に気になるのはその内容ではなく、印刷されている文字の方です。地図や海図の製作には早くから写植が導入されていたようですが、このページの文字も写植で打たれており*1、写真植字機研究所(のちの写研)の明朝体のように見えます。しかしよく見てみると、2行目の「習」や3行目の「場」など、普通の明朝体とはちょっと変わっていませんか…?
どうもこれは、この『同型類字集 明朝体』で示されているとおりに、写植の明朝体を修整しているようなのです。さらによく見てみると、1行目の「據」や2行目「編」などの撥ねも、しっかり取り除かれていることがわかります。写植で普通に印字したものを器用に修整したんでしょうか。それとも独自仕様の明朝体の文字盤なんてものがあったんでしょうか。いずれにせよ、なかなかのこだわりです。
この『同型類字集』と関係あるのかないのか、『地図用文字』なる本があると知ったので、こちらも気になる今日このごろです。
機械彫刻標準書体がまたプチ盛り上がりをしてたようなので、たしか昔見本帳買ったような……と思ったら全然違った。『地図用文字』だった。地図に使われる書体は「等線体」といって均等な太さの線で書かれた文字ですが郡名には隷体を使うみたい pic.twitter.com/kAH0y4Jhb9
— ばるぼら (@bxjp) 2015, 11月 24
*1:巻末の「扁旁部首」もそうです。
同型類字集 等線体
何ヶ月か前に、『同型類字集 等線体』『同型類字集 明朝体』という小冊子を入手しました。 陸地測量部が戦時中に作ったもので、地図に書き入れる文字のレタリング見本・字体見本のようです。
冊子を綴じてあったホチキスが錆びていたこともあり、せっかくなのでスキャンして公開してみることにしました。今日はまず、昭和17年(1942年)12月発行の『同型類字集 等線体』です。部屋にある家庭用複合機(ブラザー DCP-J940N)を使い、600 dpi のグレースケールでスキャンしました。
上の画像をクリックすると、Flickr でファイルが閲覧できます。なお、入手時点で冊子末尾の数ページが欠落していたため、「扁旁部首」の6画の途中で終わっています。PDF にまとめたものも用意しましたので、下のページからダウンロードしてください。
この等線体は見ての通り、ポキポキとした直線的な感じの書体になっています。フトコロも広く、払いがぐっと左右外側に向かっているのも特徴的です。昔のレタリング文字ではよく見ますが、そういえば現代のフォントにはこういうデザインあまりないですね。字体に関していえば、常用漢字ほどではありませんが、ちょこちょこと省略・簡略化がみられます。違いがさりげなさすぎて見落としそうなものも……。
はてなブログに引っ越してきました
いままでこのブログははてなダイアリーというサービスを使っていましたが、同社の後継サービスであるはてなブログの機能が充実してきたようなので、こちらに引っ越してきました。URL は以下のように変更になります。
過去記事はもちろんのこと、記事へ寄せていただいたコメントやはてブ・はてなスターも、一緒に移行されているはずです。 また、旧URLへアクセスすると、自動的に新URLにリダイレクトされるようになっています。 昔の記事については、いま読み返してみるといろいろとはずかしいことが書いてあったりしそうですが(読み返せてない)、恥を忍んでそのまま晒しておくことにします。 リンク切れや古い情報もそのままになっていますので、ご注意ください。
引っ越してきたのでこれからバリバリ記事書きます!!などというわけでもないのですが、よろしくお願いいたします。