ufo2ft/postProcessor.py#L168-L191 に大いなるヒントがある。特に
cff.charset = [rename_map.get(n, n) for n in cff.charset]
がキーのようだ。これを確認しよう。このために参照する仕様書は 5176.CFF.pdf である。「13 Charsets」を見れば良い。Top DICT INDEX
のオフセット位置のバイナリからこの構造を構築することになる。一連の SIDs
が得られるが、それに対応する具体的な文字列は「10 String INDEX」から取得することになる。
Charsets の読み取り
この Charsets
の読み取りは fontTools
においては fontTools/cffLib/__init__.py#L1455-L1513 で実行されている。仕様書の通りに String INDEX
をパースしているだけなので詳細は割愛する。
Charsets と String INDEX の保存
これが保存される時にどうなっているか?ということであるが、TopDictCompiler
クラスの動作を見ることになる。fontTools/cffLib/__init__.py#L2327-L2335 を頼りに、CharsetCompiler
を見ると、fontTools/cffLib/__init__.py#L1521-L1522 でバイナリデータへと pack
している。
String INDEX 再構築のトリック
実はこの時に String INDEX
が再構築されるという面白い動きをしている。どういうことかと言うと、compile
を実行する際に事前に fontTools/cffLib/__init__.py#L158-L159 のように空の IndexedStrings
を作っておいてここに新しく文字列データを詰めていくのである。具体的には packCharset0
時に getSIDfromName
で新しい SID
を決めながら Charsets
のバイナリを作っていく。つまり IndexedStrings.getSID
の中で fontTools/cffLib/__init__.py#L2734-L2737
SID = len(self.strings) + cffStandardStringCount
self.strings.append(s)
self.stringMapping[s] = SID
のように、逐次新しい SID
を発行しているのである。
まとめ
ということで、cff.charset
を更新しておくと、compile
時に String INDEX
も作り直されて保存されることが分かった。これで cff.charset
がグリフ名を変更するキーになることが確認できた。
実際
topDict.charset = new_charset ttFont.save(new_path)
という snippet でグリフ名を変えることができた。一方で、冒頭の ufo2ft/postProcessor.py
では ttFont.setGlyphOrder
をしていて少し気になる。
グリフオーダーも一緒に変更しているのは・・・?
これは推測だが、fontTools/cffLib/__init__.py#L185-L188 の
for topDict in self.topDictIndex: if not hasattr(topDict, "charset") or topDict.charset is None: charset = otFont.getGlyphOrder() topDict.charset = charset
に見られるように、ttFont.getGlyphOrder
と topDict.charset
を一致させたいように見える。実際 ttFont.getGlyphOrder
の実装を見ると fontTools/ttLib/ttFont.py#L431-L453 のように
if 'CFF ' in self: cff = self['CFF '] self.glyphOrder = cff.getGlyphOrder()
となっている。丁寧に追いかけると ttFont["CFF "].cff. topDictIndex.getGlyphOrder()
を呼んでいることが分かる。それは fontTools/cffLib/__init__.py#L2561-L2563 より、
def getGlyphOrder(self): """Returns a list of glyph names in the CFF font.""" return self.charset
ということで、一周回って topDict.charset
へと戻ってきた。
本当のまとめ
以上から、手堅いところとしては
otf.setGlyphOrder([rename_map.get(n, n) for n in otf.getGlyphOrder()]) cff.charset = [rename_map.get(n, n) for n in cff.charset]
の “同期” をさせた上で TTFont.save
してあげるのが良さそうだ。まぁ、でも、この snippet はたぶんグリフ名を書き換える時に実行してすぐに保存したほうが良い。変えたままで TTFont
に触り続けるとした場合、GSUB
や GPOS
でのグリフ名に不整合を起こしていそうだからだ。