RORO

ふつうの日記(移転したい)

文字セットの違いで生じる大量のフォントを自動で整理する

モリサワパスポートには大量のフォントが収録されているのだけど、その中には単なる収録文字セットの違い (Std, Pro, Pr5, Pr6など) によるvariantsも数多くある。例えば以下のような感じだ:

  • A-OTF リュウミン Pro (Adobe Japan 1-4)
  • A-OTF リュウミン Pr5 (Adobe Japan 1-5)
  • A-OTF リュウミン Pr6 (Adobe Japan 1-6)
  • A-OTF リュウミン Pr6N (Adobe Japan 1-6, JIS新字形)

これらのvariantsが統制なく使われると、結局、データをやりとりする全員がすべてのvariantsをインストールしなければならなくなる。これを避けるために、なるべく最小限のvariantsのみが使われるようにしておきたい。

具体的な方針としては…

  • スーパーセット(例: Pr6N)のファミリが存在するなら、サブセット(例: ProN)のファミリは捨てる。つまり常にスーパーセットを使うようにする。(大きめの文字セットを使うと計算資源も食うようだけど、問題ないだろう)。
  • 字形の違い(例: ProとProN)があるものについては、新字形のみを保持する。
  • ついでにフォルダ分けもしておく: フォントファミリ名でフォルダ分けする。つまり、同じファミリ名のサブファミリ違いを、同一のフォルダにまとめる。

…という感じにしようと思う。さすがにこれを手動でやるわけにはいかないので、作業を自動化した。

Pythonスクリプトはこんな感じ:

import unicodedata
import re
import os.path
import glob
import shutil
from fontTools import ttLib
def get_family_and_subfamily(path):
font = ttLib.TTFont(path, fontNumber=0)
family = None
subfamily = None
for record in font['name'].names:
name_str = record.toUnicode()
if (record.langID == 0 and not family) or record.langID in [11, 1041]:
if record.nameID == 16:
family = name_str
elif record.nameID == 17:
subfamily = name_str
if not family:
for record in font['name'].names:
name_str = record.toUnicode()
if (record.langID == 0 and not family) or record.langID in [11, 1041]:
if record.nameID == 1:
family = name_str
elif record.nameID == 2:
subfamily = name_str
if family:
famiy = family.strip()
family = unicodedata.normalize("NFKC", family)
# m = re.match("(.*?)-?(W?\d+|B|DB|EL|H|M|B|U|S|EU|L|R|D|ORU|Bold|Light|Medium|Regu|Thin|Heavy|Ultra|EB|UB|HS|HL|E)$", family)
# if m:
# family = m.group(1).strip()
return (family, subfamily)
def split_aj_coverage(family):
m = re.match("(.*?)\s*((Std|Pro|Pr5|Pr6|Min|Upr)N?)$", family)
if m:
pure_family = m.group(1)
aj_coverage = m.group(2)
return (pure_family, aj_coverage)
else:
return (family, None)
def iter_fonts():
for path in glob.glob("/Library/Fonts/*.otf"):
(family, subfamily) = get_family_and_subfamily(path)
if family is None:
print("fatal: " + path)
continue
(pure_family, aj_coverage) = split_aj_coverage(family)
yield (family, pure_family, aj_coverage, subfamily, path)
def filter_variants(variants):
r = {}
for aj in ["Pr6", "Pr5", "Pro", "Std"]:
aj_n = aj + "N"
if aj_n in variants:
r[aj_n] = variants[aj_n]
break
if aj in variants:
r[aj] = variants[aj]
break
if "Upr" in variants:
r["Upr"] = variants["Upr"]
if "Min" in variants:
r["Min"] = variants["Min"]
if (not r) and None in variants:
r[None] = variants[None]
return r
if __name__ == "__main__":
fam_aj_path_map = {}
for (family, pure_family, aj, subfamily, path) in iter_fonts():
key = (pure_family, subfamily)
fam_aj_path_map.setdefault(key, {})
fam_aj_path_map[key][aj] = (family, path)
for ((pure_family, _), aj_path_map) in fam_aj_path_map.items():
variants = filter_variants(aj_path_map)
for (family, path) in variants.values():
dirname = os.path.join('fonts', family)
if not os.path.exists(dirname):
os.makedirs(dirname)
shutil.copy(
path,
os.path.join(dirname, os.path.basename(path))
)