Skip to content

Latest commit

 

History

History
executable file
·
136 lines (89 loc) · 10.3 KB

scripting-glyphs-part-2.md

File metadata and controls

executable file
·
136 lines (89 loc) · 10.3 KB

Glyphsでスクリプト・2

(Original page)


by Rafał Buchner & Rainer Scheichelbauer


1 December 2020
Published on 28 June 2012


第1回では、フォントとグリフ情報の出力方法を習いました。今回はもう一歩進んでフォントを実際に操作しようと思います。お好きなフォントをコピーしてPython筋肉をウォームアップしましょう。


このチュートリアルは、最初にGlyphsでスクリプト・1を読み終わっていることが前提です。

グリフとレイヤー

まず、Glyphsで文字がどう構成されているかを思い起こすことから始めましょう。全てのパス、アンカー、ガイドラインといった、文字の作図や編集で扱われるものは、「グリフ」の単純なプロパティではありません。むしろ、それらはグリフが保持する「レイヤー」の中の1つに属しています。レイヤーには手動で追加されたものとマスターレイヤー (補間で使用されるもの)の2つがあります。

レイヤー選択

さて、手を付ける前に、選択されたレイヤーはどれかを判断しなくてはなりません。その方法を説明します。マクロパネルを開き、次のように入力します。

print( Glyphs.font.selectedLayers )

するとこんな感じのものが出力されます。

(
    "GSLayer <0x7fe471526320>: [Bold Italic] (A)",
    "GSLayer <0x7fe47152fbb0>: [Bold Italic] (B)",
    "GSLayer <0x7fe471530d30>: [Bold Italic] (C)"
)

それで、今何をしたのでしょうか? まず、現在開いている、つまり最前面のフォントの Glyphs オブジェクトを取得してその font プロパティを参照しました。フォントを開いていないとエラーになります。確かに、結果(「GSLayer…」)はちょっと意味が分からないものですが、これはGlyphsがレイヤーを呼び出す方法です。16進数のIDコードの後に、レイヤー名、そしてそれぞれのグリフ名が続きます。

幸いにも、これらを直接取り扱う必要はありません。Pythonに仕事をさせればよいのです。ということで、最初の行は次のようにしておきます。

myLayers = Glyphs.font.selectedLayers

パスの参照

これをもう少し拡張してみましょう。

myLayers = Glyphs.font.selectedLayers
for thisLayer in myLayers:
    print( thisLayer.parent.name )
    print( thisLayer.paths )

マクロパネルでこれを実行すると、次のようなものが出力で表示されます。

A
(<GSPath 57 nodes and 29 segments>, <GSPath 10 nodes and 4 segments>)
B
(<GSPath 53 nodes and 23 segments>, <GSPath 42 nodes and 16 segments>)
C
(<GSPath 57 nodes and 23 segments>)

これは、何をしたのでしょうか? font から selectedLayers を取り出すことで、ユーザが選択した全てのレイヤーを取得し、それを変数 myLayers に代入しました。

2行目で、myLayers に含まれる全てのレイヤーをループ処理します。レイヤーは1つずつ次々に処理されます。まず処理中のレイヤーを thisLayer と名付け、それから thisLayer の親の名前を取得してマクロパネルに表示し、最後に thisLayer にあるパスを取得して同じくマクロパネルにそれを表示します。その後ループは myLayers 内の次のレイヤーに移って、一連の同じ処理がまた始まります。それは myLayers 内の最後のレイヤーが処理されるまで続きます。

ここにこっそり忍び込ませたものがあります。parent です。ピリオドの後にサブオブジェクト名を追加して、オブジェクト内のサブオブジェクトにアクセスするという話は覚えていますか? 現在Glyphsで開いている全てのフォントを参照するには、Glyphs.fonts と入力します。最初に開いたフォントのファミリー名を知るには、Glyphs.fonts[0].familyName などと入力します。こうしてオブジェクトのツリーを掘り下げることができます。しかし、あるオブジェクトをすでに参照中でその階層を1段「さかのぼる」必要がある場合もあります。その場合に、オブジェクトの parent を参照します。

この例では、あるレイヤーを thisLayer として取り込んで、そのレイヤーがどのグリフのものであるかを知りたいので、そのレイヤーの親である thisLayer.parent を参照します。そして最後にこの親であるグリフの名前 thisLayer.parent.name を参照し、その結果を print コマンドに渡します。

最後の行が、「聖杯」、つまりフォントを構成するパスそのものに到っているもので、私たち全員にとって重要です。次のステップで私たちはパスにとんでもないことをやるので、フォントのコピーを確実に取っておいてください。警告しましたよ。

パスとノード

さあ、選択したレイヤーに何かヤバイことをしたいと思います。全てのノードを左右へランダムに動かしてみましょう。次のようにやります。Pythonで正か負の値の乱数を生成して最初に選択したレイヤーの最初のパスの最初のノードのx座標に加算します。そして他の全てのノード、パス、レイヤーに対してそれを繰り返します。

Pythonには乱数の生成機能がありますが、これを使うには特別な招待状が必要です。これを「モジュールのインポート」とか「ライブラリのインポート」と呼びます。そこで、まず乱数生成機能をインポートすることから始めます。

import random

ところでここだけの話、import random する時は、その度に乱数の「種(seed)」関数を呼び出すのが良いです。

random.seed()

seed の後ろのカッコに気がつきましたか? このカッコはいわゆる「メソッド」を表します。そうです、オブジェクトは別のオブジェクトを含むことができますが、メソッド(または「関数」)を含むこともできます。メソッドは所属するオブジェクトになんらかの動きをします。seed() 関数は乱数生成機能に対しつべこべ言わずに「本当に」ランダムな値を出力するよう指示します。そうしておかないと、乱数生成機能が再び同じ値を出力してしまう可能性があって、それは実に望ましくありません。

さてさて、いよいよ全ての選択されたレイヤーと、全てのパス、そして全てのノードを当たってやるべき処理を行います。

import random
random.seed()

myLayers = Glyphs.font.selectedLayers
for thisLayer in myLayers:
    for thisPath in thisLayer.paths:
        for thisNode in thisPath.nodes:
            thisNode.x += random.randint( -50, 50 )

最後の行以外は、説明は不要でしょう。変数 thisNode にはパス上のオンカーブ点またはオフカーブ点が代入されます。前の行で全てのノードを反復処理しています。プロパティの x は、もちろんそのノードのx座標です。

演算子 += は「左側の変数に右側の値を足す」ことを意味しており、例えば、x+=5 では x の値を 5 増やします。そのためこの += は「インクリメント演算子」と呼ばれます。これは x=x+5 をもっと短く効率的に記述する方法です。

Pro Tip

クラスに含まれるメソッドやオブジェクトを確認するには、Pythonの組み込み関数である help() を実行します。例えば、import random した後に help(random) を実行できます。help() 関数はそのカッコ内に記述されたもののマニュアルを出力します。
help(random) で出力されるテキストの量に辟易するときは、代わりとしてマニュアルを randint に限定して help(random.randint) を実行してみてください。

さて、乱数の部分についてです。まず、数行前でインポートした random モジュールを使います。そしてその中のメソッド randint() で「ランダムな整数」を取得します。他のメソッドと同様に、randint() の後ろにはカッコがついています。メソッドにこのカッコ内から入力値を渡すと、その入力に基づいて出力が得られます。randint() メソッドは、カンマで区切られた2つの値を「最小値」と「最大値」として受け取り、その最小値と最大値の間にある整数を1つランダムに返します。最小値 -50 、最大値 50randint() に入力し、乱数を出力として受け取りたいわけです。

つまり、最後の行は、それぞれのノードのx座標を取得してその値に-50〜50の範囲にある乱数値を加える、ということをしています。おわかりいただけたでしょうか?

今のところまで大丈夫ですか? OK? それで、次は何をしましょう? このスクリプトを「スクリプト」メニューの項目に入れて、好きな時にいつでも使えるようにしましょう。どうやって? それはGlyphsでスクリプト・3でご説明します。


Update 2014-10-04: 第1回と第3回へのリンクを追加。
Update 2016-12-08: 書式を修正、廃止になった警告を削除、help関数に関するpro tipを追加。
Update 2019-02-06: 文章と書式を微調整。
Update 2019-02-12: 誤記訂正。
Update 2020-12-02: Python3に更新。