フォント合成 (3)
ここでは、Python3 スクリプトを使って、CID フォント同士を合成します。
CID フォントの場合、CID とグリフの定義は、Adobe-Japan1-6 などのような、
[Registry]-[Ordering]-[Supplement] (ROS) で指定されます。
Supplement は、いわゆるバージョンのようなもので、数字が増えると、グリフが増えます。
2つのフォントで、Registry と Ordering が同じであれば、互換性があるということなので、例えば、Adobe-Japan1-6 のフォントに対して、Adobe-Japan1-3 のフォントを合成する場合、同じ CID のグリフをそのまま置き換えればいいということになります。
もしも、一部で合成したくないグリフがある場合は、あらかじめ、CID フォントのままの状態で、不要なグリフを削除しておいて、一旦 SFD 形式で出力し、それを合成するという方法が使えます。
CID フォント同士で合成する場合は、ベースフォントの Supplement が、合成するフォントの Supplement と同じか、それより大きいことが必須条件となりますが、ベースフォントと重複するグリフを置き換えればいいだけなので、前回の合成よりは処理が簡単になります。
EM 値も、OpenType では、基本的に 1000 で固定となっているので、2つのフォント間で EM 値を調整する必要もありません。
※ FontForge 上では、CID フォントを開いて、「定義済みのグリフのみ表示」が ON の状態で一覧を表示すると、「移動」などでグリフを検索した時に正しい位置に移動しないので、何かしらおかしい場合は、OFF にした状態で一覧表示してください。
CID フォントの場合、CID とグリフの定義は、Adobe-Japan1-6 などのような、
[Registry]-[Ordering]-[Supplement] (ROS) で指定されます。
Supplement は、いわゆるバージョンのようなもので、数字が増えると、グリフが増えます。
2つのフォントで、Registry と Ordering が同じであれば、互換性があるということなので、例えば、Adobe-Japan1-6 のフォントに対して、Adobe-Japan1-3 のフォントを合成する場合、同じ CID のグリフをそのまま置き換えればいいということになります。
もしも、一部で合成したくないグリフがある場合は、あらかじめ、CID フォントのままの状態で、不要なグリフを削除しておいて、一旦 SFD 形式で出力し、それを合成するという方法が使えます。
CID フォント同士で合成する場合は、ベースフォントの Supplement が、合成するフォントの Supplement と同じか、それより大きいことが必須条件となりますが、ベースフォントと重複するグリフを置き換えればいいだけなので、前回の合成よりは処理が簡単になります。
EM 値も、OpenType では、基本的に 1000 で固定となっているので、2つのフォント間で EM 値を調整する必要もありません。
※ FontForge 上では、CID フォントを開いて、「定義済みのグリフのみ表示」が ON の状態で一覧を表示すると、「移動」などでグリフを検索した時に正しい位置に移動しないので、何かしらおかしい場合は、OFF にした状態で一覧表示してください。
スクリプト
>> mergefont-cid.py (右クリックで保存してください)
コマンドライン上で、引数に「出力ファイル名」「ベースフォント」「合成フォント (複数可)」を順に指定して、実行してください。
コマンドライン上で、引数に「出力ファイル名」「ベースフォント」「合成フォント (複数可)」を順に指定して、実行してください。
$ python3 mergefont-cid.py output.sfd base.otf merge.otf
#!/usr/bin/python3 #-------------------------------------------- # CID フォント同士を合成 # # mergefont-cid.py [OUTPUT_FONT] [BASE_FONT] [MERGE_FONT...] # # - 出力/入力ともに、.sfd も可。 # # OUTPUT_FONT # 出力ファイル名。 # 拡張子が .sfd なら SFD 形式。それ以外は拡張子によって、各フォント出力。 # BASE_FONT # ベースフォントファイル名。CID フォントのみ。 # MERGE_FONT # 合成するフォントファイル名。複数指定可。CID フォントのみ。 #-------------------------------------------- import fontforge import os import sys if len(sys.argv) < 4: print('<Usage> mergefont-cid.py [OUTPUT_FONT] [BASE_FONT] [MERGE_FONT...]') sys.exit(0) class MergeGlyph: GSUB_TYPES = ("Substitution", "AltSubs", "MultSubs", "Ligature") GPOS_TYPES = ("Position", "Pair") # ベースフォントのグリフリスト作成 def _create_base_glyph_list(self): self.basename = {} for i in range(self.basefont.cidsubfontcnt): self.basefont.cidsubfont = i for gname in self.basefont: self.basename[gname] = i # 合成するグリフのリスト作成 (key=rep, val=cidsubfont) def _create_rep_glyph_list(self): self.gname = {} for i in range(self.repfont.cidsubfontcnt): self.repfont.cidsubfont = i for gname in self.repfont: if (gname in self.basefont) and (gname in self.repfont): self.gname[gname] = i # グリフのコピー def _copy_glyph(self, gname, subfont): grep = self.repfont[gname] gbase = self.basefont[gname] self.repfont.cidsubfont = subfont self.repfont.selection.select(grep) self.repfont.copy() self.basefont.cidsubfont = self.basename[gname] self.basefont.selection.select(gbase) self.basefont.paste() return [grep, gbase] # base 用 lookup 名取得 def _get_lookup_base(self, name): addname = self.repfont.cidfontname # サブテーブル sname = addname + '-' + name if sname in self.baselookupsub: return sname # 親 lookup reppname = self.repfont.getLookupOfSubtable(name) basepname = addname + '-' + reppname # 親 lookup 作成 if basepname not in self.baselookup: ptype,flags,val = self.repfont.getLookupInfo(reppname) self.basefont.addLookup(basepname, ptype, tuple(), val) self.baselookup[basepname] = 1 # サブテーブル作成 self.basefont.addLookupSubtable(basepname, sname) self.baselookupsub[sname] = 1 return sname # base の GPOS/GSUB 情報削除 def _del_gpos_gsub(self, gbase): for t in gbase.getPosSub('*'): gbase.removePosSub(t[0]) # rep の GSUB 情報をコピー def _copy_rep_gsub(self, grep, gbase): for t in grep.getPosSub('*'): ltype = t[1] if ltype not in self.GSUB_TYPES: continue # 置換グリフ名 # (rep 内で存在しないグリフを参照している場合は除外) val = [] for name in t[2:]: if name in self.repfont: val.append(name) if not val: continue # 追加 baselname = self._get_lookup_base(t[0]) if ltype == 'Substitution': val = val[0] else: val = tuple(val) gbase.addPosSub(baselname, val) # rep の GPOS 情報をコピー def _copy_rep_gpos(self, grep, gbase): for t in grep.getPosSub('*'): ltype = t[1] if ltype not in self.GPOS_TYPES: continue baselname = self._get_lookup_base(t[0]) val = t[2:] if ltype == 'Position': gbase.addPosSub(baselname, val[0], val[1], val[2], val[3]) else: gbase.addPosSub(baselname, val[0], val[1], val[2], val[3], val[4], val[5], val[6], val[7], val[8]) # ベースフォント開く def open_base(self, filename): print('# load ...' + filename) self.basefont = fontforge.open(filename) if not self.basefont.is_cid: print('[!] "{0}" is not CID'.format(filename)) sys.exit(0) self.baselookup = {} self.baselookupsub = {} self._create_base_glyph_list() self._check_unicode() # 合成フォント開く def _open_merge(self, filename): print('# load ... ' + filename) self.repfont = fontforge.open(filename) if not self.repfont.is_cid: print('[!] "{0}" is not CID'.format(filename)) sys.exit(0) if self.repfont.cidregistry != self.basefont.cidregistry \ or self.repfont.cidordering != self.basefont.cidordering \ or self.repfont.cidsupplement > self.basefont.cidsupplement: print('[!] "{0}" ROS error'.format(filename)) sys.exit(0) self._create_rep_glyph_list() # 縦書きメトリクス ON if (not self.basefont.hasvmetrics) and self.repfont.hasvmetrics: self.basefont.hasvmetrics = 1 # 合成処理 def merge(self, filename): self._open_merge(filename) for gname,subfont in self.gname.items(): print(gname) grep, gbase = self._copy_glyph(gname, subfont) self._del_gpos_gsub(gbase) self._copy_rep_gsub(grep, gbase) self._copy_rep_gpos(grep, gbase) self.repfont.close() # Unicode 重複を回避 def _check_unicode(self): print('# check unicode') # リスト作成 unimap = {} for i in range(self.basefont.cidsubfontcnt): self.basefont.cidsubfont = i for gname in self.basefont: g = self.basefont[gname] if g.unicode == -1: continue # 異体字の場合は除外 if g.altuni: flag = False for t in g.altuni: if t[1] != -1: flag = True break if flag: continue if g.unicode in unimap: unimap[g.unicode].append(gname) else: unimap[g.unicode] = [gname] # 重複削除 for uni,names in unimap.items(): if len(names) > 1: uniname = fontforge.nameFromUnicode(uni) for gname in names: if gname != uniname: self.basefont[gname].unicode = -1 print('x "{0}" U+{1:04X}'.format(gname, uni)) # 出力 def output(self, filename): root,ext = os.path.splitext(filename) print('# output ... ' + filename) if ext.lower() == ".sfd": self.basefont.save(filename) else: self.basefont.generate(filename, flags=('opentype','short-post')) # o = MergeGlyph() o.open_base(sys.argv[2]) for fname in sys.argv[3:]: o.merge(fname) o.output(sys.argv[1])
使い方
- ベースフォント・合成フォント共に、CID フォントである必要があります。
- 2つのフォントの ROS が一致、または、Supplement が ベースフォント >= 合成フォント である必要があります。
- 合成フォントに含まれるすべての (定義済み) グリフが、ベースフォントのグリフと置き換わります。
(ベースフォントと重複しないグリフがもしあった場合は、対象外となります) - グリフの GSUB/GPOS 情報は、ベースフォントのグリフの情報を削除した上で、合成フォントのものがコピーされます。
(参照されていない置換先グリフの削除は行われません) - ベースフォント上で、Unicode が重複するグリフがある場合、重複をチェックした上で、その Unicode の正式名と異なるグリフ名の Unicode 値を削除します。