フォント合成 (2)
FontForge では、FontForge 独自のスクリプトか、Python3 スクリプトを使って、フォントを操作することができます。
「フォントの統合」による色々な問題を解決するため、スクリプトを使って、フォントを合成させることにします。
ここでは、Python3 スクリプトを使い、ベースフォント (CID フォント以外) に対して、他のフォントのグリフを合成させます。
合成先が CID フォントで、CID フォントのまま合成したい場合は、サブフォントや CID などの問題があるため、別の形で合成する必要があります。
CID フォント同士の合成は、次回で行います。
「フォントの統合」による合成では、「置き換えたいグリフが含まれたフォント+ベースフォント」という形で合成しましたが、今回は、ベースフォント+[置換/追加したいグリフが含まれたフォント...] という形で合成していきます。
なお、ベースフォントは CID 以外のフォントである必要があります。
合成するフォントは、CID フォントでもそれ以外でも OK です。
「フォントの統合」による色々な問題を解決するため、スクリプトを使って、フォントを合成させることにします。
ここでは、Python3 スクリプトを使い、ベースフォント (CID フォント以外) に対して、他のフォントのグリフを合成させます。
合成先が CID フォントで、CID フォントのまま合成したい場合は、サブフォントや CID などの問題があるため、別の形で合成する必要があります。
CID フォント同士の合成は、次回で行います。
「フォントの統合」による合成では、「置き換えたいグリフが含まれたフォント+ベースフォント」という形で合成しましたが、今回は、ベースフォント+[置換/追加したいグリフが含まれたフォント...] という形で合成していきます。
なお、ベースフォントは CID 以外のフォントである必要があります。
合成するフォントは、CID フォントでもそれ以外でも OK です。
スクリプト
>> mergefont.py (右クリックで保存してください)
コマンドライン上で、引数に「出力ファイル名」「ベースフォント」「合成フォント (複数可)」を順に指定して、実行してください。
コマンドライン上で、引数に「出力ファイル名」「ベースフォント」「合成フォント (複数可)」を順に指定して、実行してください。
$ python3 mergefont.py output.sfd base.ttf merge.ttf ...
#!/usr/bin/python3 #-------------------------------------------- # フォントの合成 (ベースフォントは非 CID) # # $ python3 mergefont.py [OUTPUT_FONT] [BASE_FONT] [MERGE_FONT...] # # - 出力/入力ともに、.sfd も可。 # # OUTPUT_FONT # 出力ファイル名。 # 拡張子が .sfd なら SFD 形式。それ以外は拡張子によって、各フォント出力。 # BASE_FONT # ベースフォントファイル名。CID フォント以外。 # MERGE_FONT # 合成するフォントファイル名。複数指定可。CID フォント可。 #-------------------------------------------- import fontforge import psMat import os import sys if len(sys.argv) < 4: print('<Usage> mergefont.py [OUTPUT_FONT] [BASE_FONT] [MERGE_FONT...]') sys.exit(0) class MergeGlyph: GSUB_TYPES = ("Substitution", "AltSubs", "MultSubs", "Ligature") GSUB_TYPES_NO_LIGA = ("Substitution", "AltSubs", "MultSubs") GPOS_TYPES = ("Position", "Pair") # グリフに、有効な Unicode 値がセットされているか def _is_glyph_unicode(self, g): if g.unicode != -1 or g.glyphname == '.notdef': return True # 代替の Unicode がある場合、(unicode, -1, 0)。 # 異体字の場合、2番目にセレクターの値が入る if g.altuni: for t in g.altuni: if t[1] != -1: return True return False # グリフリストに追加 def _add_glpyh_list(self,subfont): for gname in self.repfont: # エンコーディングから切り離されているグリフは対象外 if gname not in self.repfont: continue # Unicode 外の同名グリフは別名にする bname = gname if (gname in self.basefont) and (not self._is_glyph_unicode(self.repfont[gname])): bname += '.rep' self.gname[gname] = (bname, subfont) # 合成するグリフリスト作成 (key=rep, val=(base,cidsubfont)) def _create_glyph_list(self): self.gname = {} if self.repfont.is_cid: for i in range(self.repfont.cidsubfontcnt): self.repfont.cidsubfont = i self._add_glpyh_list(i) else: self._add_glpyh_list(-1) # base の全グリフの GSUB 置換参照リストを作成 # (key=置換先グリフ名, val=参照元のグリフ名の配列) def _create_gsub_ref_list(self): ret = {} for gname in self.basefont: if gname not in self.basefont: continue for t in self.basefont[gname].getPosSub('*'): if t[1] in self.GSUB_TYPES_NO_LIGA: for repname in t[2:]: if repname not in ret: ret[repname] = [gname] else: if gname not in ret[repname]: ret[repname].append(gname) return ret # (base) GSUB 置換先のグリフを削除 # basename: 置換情報が設定されているグリフ名 # delname: 削除するグリフ名 def _del_gsub_glyph(self, basename, delname): # すでに削除済みの場合 if delname not in self.basefont: return # 対象に Unicode 値がある場合は削除しない g = self.basefont[delname] if self._is_glyph_unicode(g): return # 対象を参照しているのが basename のみの場合、削除 if delname not in self.gsubref: flag = 1 else: names = self.gsubref[delname] if len(names) == 1 and names[0] == basename: flag = 2 else: flag = 0 if flag: self.basefont.removeGlyph(delname) print("- del: " + delname) if flag == 2: del self.gsubref[delname] # グリフの作成とコピー def _copy_glyph(self, repname, basename, subfont): grep = self.repfont[repname] # 新規グリフ if (basename not in self.basefont) or repname != basename: code = grep.unicode # Unicode の定義名と異なる場合、 # 別グリフで Unicode が重複しているため、Unicode をセットしない if code != -1 and fontforge.nameFromUnicode(code) != repname: code = -1 g = self.basefont.createChar(code, basename) if grep.altuni: g.altuni = grep.altuni gbase = self.basefont[basename] # アウトラインとヒント情報コピー if subfont != -1: self.repfont.cidsubfont = subfont self.repfont.selection.select(grep) self.repfont.copy() self.basefont.selection.select(gbase) self.basefont.paste() # em が異なる場合、拡大縮小 if self.em_scale: gbase.transform(psMat.scale(self.em_scale), ('round',)) return [grep, gbase] # base 用 lookup 名取得 def _get_lookup_base(self, name): if self.repfont.is_cid: addname = self.repfont.cidfontname else: addname = self.repfont.fontname # サブテーブル 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 の GSUB/GPOS 情報を削除 def _del_base_possub(self, gbase): for t in gbase.getPosSub('*'): ltype = t[1] # 合字以外の GSUB は置換先グリフを削除 if (ltype in self.GSUB_TYPES) and ltype != 'Ligature': for name in t[2:]: self._del_gsub_glyph(gbase.glyphname, name) 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(self.gname[name][0]) 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): scale = self.em_scale for t in grep.getPosSub('*'): ltype = t[1] if ltype not in self.GPOS_TYPES: continue val = list(t[2:]) if ltype == 'Position': if scale: for i in range(4): val[i] = round(val[i] * scale) baselname = self._get_lookup_base(t[0]) gbase.addPosSub(baselname, val[0], val[1], val[2], val[3]) else: # pair if val[0] not in self.gname: continue val[0] = self.gname[val[0]][0] if scale: for i in range(1, 9): val[i] = round(val[i] * scale) baselname = self._get_lookup_base(t[0]) 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 base ...' + filename) self.basefont = fontforge.open(filename) if self.basefont.is_cid: print('[!] CID font is not supported') sys.exit(0) self.baselookup = {} self.baselookupsub = {} self.gsubref = self._create_gsub_ref_list() # 合成フォント開く def _open_merge(self, filename): print('# load merge ... ' + filename) self.repfont = fontforge.open(filename) self._create_glyph_list() # 縦書きメトリクス ON if (not self.basefont.hasvmetrics) and self.repfont.hasvmetrics: self.basefont.hasvmetrics = 1 # EM 拡大率 if self.basefont.em == self.repfont.em: self.em_scale = 0 else: self.em_scale = self.basefont.em / self.repfont.em print('# em: {0} => {1}'.format(self.repfont.em, self.basefont.em)) # 合成処理 def merge(self, filename): self._open_merge(filename) for repname,val in self.gname.items(): basename = val[0] subfont = val[1] if repname == basename: print(repname) else: print(repname + ' => ' + basename) grep, gbase = self._copy_glyph(repname, basename, subfont) self._del_base_possub(gbase) self._copy_rep_gsub(grep, gbase) self._copy_rep_gpos(grep, gbase) self.repfont.close() # 出力 def output(self, filename): if self.basefont.encoding == 'Custom': self.basefont.encoding = 'UnicodeFull' 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 フォントは指定できません。
- 合成するフォントは、CID フォントでも OK です。
- ベースフォントと合成フォントで EM 値が異なる場合、ベースフォントの EM に合うように、グリフが拡大縮小されます。
- 合成するフォントに含まれているすべてのグリフが、ベースフォントのグリフと置き換え、または追加されます。
- ベースフォント内に、合成するグリフと同名のグリフが含まれる場合、基本的には合成するグリフに置き換えられますが、有効な Unicode 値が指定されていないグリフの場合は、末尾に ".rep" を付けた別名のグリフとして追加されます。
(Unicode 外のグリフは、各フォントで、グリフ名とグリフ形状が一致しない場合があるため) - グリフを追加する際、そのグリフで指定されている Unicode 値の正式グリフ名と、元のグリフのグリフ名が異なる場合、Unicode 値を指定しない状態で追加されます。
(合成フォント側で Unicode 値が重複しているグリフの対策) - 合成フォントのグリフが、ベースフォントのグリフと置き換わる場合、ベースフォントのグリフに設定されていた GSUB/GPOS 情報は削除され、合成フォントのグリフの情報がコピーされます。
この時、ベースフォント内で他のグリフから参照されない置き換え先グリフは、自動で削除されます。 - 合成フォントから GSUB/GPOS の情報をコピーする際、その lookup 名は、「合成フォントの名前-合成フォントの lookup 名」という名前で新規追加されます。
- GPOS の値は、EM が異なる場合、自動調整されます。