#!/usr/bin/python3 ''' 変換元フォントの情報を元に、makeotf 用の features ファイルを作成する。 $ ./make-features.py [ ] > features.fea suppliment: ROS の3番目の数字を指定 (3〜7)。 mapfile: remap-*.py で出力した CID のマッピングファイル。 aj-gsub.fea: Adobe-Japan1-* の GSUB 用 features ファイル。 以下のファイルをダウンロードして指定。 https://github.com/adobe-type-tools/Adobe-Japan1 - GSUB/aj17-gsub-jp04.fea または aj17-gsub.fea [!] 最終的に JIS2004 のフォントとして作成するなら、aj17-gsub-jp04.fea。 JIS90 として作成するなら、aj17-gsub.fea。 spot-VORG: 変換元フォントの VORG 情報のテキスト (VORG が必要なければ省略、または "")。 以下のコマンドで出力したファイルを指定する。 $ spot -t VORG [FONTFILE] > vorg.txt spot-GPOS: 変換元フォントの GPOS 情報のテキスト (GPOS が必要なければ省略、または "")。 以下のコマンドで出力したファイルを指定する。 $ spot -t GPOS=7 [FONTFILE] > gpos.txt ''' import sys import re arglen = len(sys.argv) if arglen < 2: print('[usage] ' + sys.argv[0] + ' [ ]') sys.exit(0) suppliment = int(sys.argv[1]) fname_map = sys.argv[2] fname_gsub = sys.argv[3] if arglen < 5: fname_vorg = '' else: fname_vorg = sys.argv[4] if arglen < 6: fname_gpos = '' else: fname_gpos = sys.argv[5] # リストの先頭の数値取得 def getcid(lst): return lst[0] class Features: GPOS_NAME = ('halt','palt','vhal','vpal') def __init__(self): self.re1 = re.compile(r'\\(\d+)\s+by\s+\\(\d+);') self.re2 = re.compile(r'\\(\d+)\s+from\s+\[(.+?)\]') self.re3 = re.compile(r'(.+?)\s+by\s+\\(\d+);') # suppliment ごとのグリフ数セット def set_suppliment(self,no): if no < 3 or no > 7: print('suppliment is 3-7.') sys.exit(1) else: num = (9354, 15444, 20317, 23058, 23060) self.gnum = num[no - 3] # map 読み込み def read_map(self,fname): f = open(fname, 'rt') lines = f.readlines() f.close() self.dat = [] self.dat.append(0) self.sidmap = {} mo = re.compile(r'(\d+)\[(\d+)\]$') for l in lines: l = l.strip() if not l: continue sp = l.split('#') if '[' in sp[0]: m = mo.match(sp[0]) dstid = int(m.group(1)) srcid = int(m.group(2)) else: dstid = int(sp[0]) srcid = None self.dat.append(srcid) # src cid -> dst cid マップ if srcid: self.sidmap[srcid] = dstid # グリフ数を合わせる datlen = len(self.dat) if datlen < self.gnum: self.dat.extend([[None,None]] * (self.gnum - datlen)) elif datlen > self.gnum: del self.dat[self.gnum:] # features(GSUB) 読み込み def read_fea_GSUB(self,fname): f = open(fname, 'rt') self.lines = f.readlines() f.close() #------ GSUB # 置換え先と置き換え元を取得 def _gsub_get_id(self,l): if ('@' in l) or (l[0] == '['): return None # 単一置換 m = re.match(self.re1, l) if m: dst = int(m.group(1)) src = int(m.group(2)) return [1, [dst], [src]] # 複数に置き換え m = re.match(self.re2, l) if m: dst = int(m.group(1)) src = [] for sl in m.group(2).split(): src.append(int(sl[1:])) return [2, [dst], src] # 合字 m = re.match(self.re3, l) if m: src = int(m.group(2)) dst = [] for sl in m.group(1).split(): dst.append(int(sl[1:])) return [3, dst, [src]] # 置き換えたテキストを取得 (type=2) def _gsub_get_reptxt(self,dst,src): o = ' substitute ' o += '\\{0} '.format(dst[0]) if len(src) == 1: o += 'by \\{0};\n'.format(src[0]) else: o += 'from [' for s in src: o += '\\{0} '.format(s) o = o[:-1] + '];\n' return o # cid リストの中から未使用のものを削除 def _gsub_del_ununsed(self,lst): out = [] for n in lst: if n >= self.gnum: continue if self.dat[n]: out.append(n) return out # 未使用の CID が使われている値を削除 def gsub_delglyph(self): i = 0 while i < len(self.lines): lraw = self.lines[i] l = lraw.strip() if not l.startswith('substitute'): i += 1 continue # 取得できないものは削除 ret = self._gsub_get_id(l[10:].lstrip()) if ret is None: del self.lines[i] continue tp = ret[0] dst = ret[1] src = ret[2] odst = self._gsub_del_ununsed(dst) osrc = self._gsub_del_ununsed(src) # dst が複数の場合、一つでも欠ければ削除 if (not odst) or (not osrc) or (tp == 3 and len(dst) != len(odst)): del self.lines[i] else: if tp == 2: self.lines[i] = self._gsub_get_reptxt(odst, osrc) i += 1 # 空のブロックを削除 def gsub_delblock(self): out = '' txt = ''.join(self.lines) mo = re.compile(r'^feature\s+(.+?)\s+\{(.*?)^\}.+?;\n', flags=re.S|re.M) while 1: m = mo.search(txt) if not m: out += txt.rstrip() + '\n' break out += txt[:m.start(0)] txt = txt[m.end(0):] # substitute を含む行があれば追加、なければ削除 lines = m.group(2).splitlines(keepends=True) flag = False for l in lines: l = l.lstrip() if l.startswith('substitute'): out += m.group(0) flag = True break # 削除した場合、"feature *;" を削除 if not flag: l = 'feature ' + m.group(1) + ';' pos = out.find(l) if pos != -1: out = out[:pos] + out[pos + len(l):] self.lines = out.splitlines(keepends=True) #----- VORG # VORG 追加 def add_vorg(self,fname): f = open(fname, 'rt') lines = f.readlines() f.close() out = [] mo = re.compile(r'\\(\d+).+?\=\s+(\d+)$') for l in lines: l = l.strip() if not l or l[0] != '\\': continue m = mo.match(l) if not m: continue cid = int(m.group(1)) pos = int(m.group(2)) if cid in self.sidmap: out.append([self.sidmap[cid], pos]) if not out: return # 追加 o = ['\n'] o.append('table vmtx {\n') for cid,pos in sorted(out, key=getcid): if cid < self.gnum: o.append(' VertOriginY \\{0} {1};\n'.format(cid, pos)) o.append('} vmtx;\n') self.lines.extend(o) #---- GPOS # GPOS feature ファイル読み込み def _read_gpos(self,fname): f = open(fname, 'rt') lines = f.readlines() f.close() self.srcfea = {} mo = re.compile(r'\\(\d+)\s+<(.+?)>;') mok = re.compile(r'\\(\d+)\s+\\(\d+)\s+(\d+);') curname = None for l in lines: l = l.strip() if not l: continue if curname: if l[0] == '#': if l.find('End Lookup') != -1: curname = None elif not l.startswith('pos') or ('@' in l): continue l = l[3:].lstrip() if curname == 'kern': m = mok.match(l) if m: self.srcfea[curname].append([int(m.group(1)), int(m.group(2)), int(m.group(3))]) else: m = mo.match(l) if m: self.srcfea[curname].append([int(m.group(1)), m.group(2)]) elif l[0] == '#' and l.find('Printing') != -1: # feature 開始 for name in self.GPOS_NAME: if l.find("'" + name + "'") != -1: curname = name if name not in self.srcfea: self.srcfea[curname] = [] break # GPOS 適用 def add_gpos(self,fname): self._read_gpos(fname) for name,lst in self.srcfea.items(): out = [] if name == 'kern': # kern for g1,g2,val in lst: if (g1 in self.sidmap) and (g2 in self.sidmap): out.append(' pos \\{0} \\{1} {2};\n'.format(self.sidmap[g1], self.sidmap[g2], val)) else: for cid,val in lst: if (cid in self.sidmap) and self.sidmap[cid] < self.gnum: out.append(' pos \\{0} <{1}>;\n'.format(self.sidmap[cid], val)) if out: out.insert(0, '\n') out.insert(1, 'feature ' + name + ' {\n') out.append('} ' + name + ';\n') self.lines.extend(out) del self.srcfea #------ c = Features() c.set_suppliment(suppliment) c.read_map(fname_map) # GSUB c.read_fea_GSUB(fname_gsub) c.gsub_delglyph() c.gsub_delblock() # VORG if fname_vorg: c.add_vorg(fname_vorg) # GPOS if fname_gpos: c.add_gpos(fname_gpos) print(''.join(c.lines))