昨今のWebデザイン界隈ですと、それは当然Webフォントを使おうということにもなろうと思いますが、注意すべき事柄に気付きましたのでここに御シェアさせていただきたく存じます。
問題
フォントの中にはたくさんのグリフ(字形)が入っていて、「あ」だったらこのグリフ、「い」だったらこのグリフ、というように対応しています。たくさんの文字を作るのは大変だから、たとえばひらがなとカタカナだけとか、第一水準漢字までとか、少ないグリフだけのフォントもあります。
Webフォントを設定していて、存在しないグリフの文字があったとすると、ブラウザが良い感じにフォールバックして別なフォントで表示してくれたりします。またCSSのunicode-range
というのを使うと、あるフォントから使うグリフをUnicodeのコードポイントの範囲で設定することができます。範囲外のグリフはやはりフォールバックされます。((unicode-range
はIE9以降とWebKit系のブラウザで対応しているようです))
ところで、もしグリフは存在しているけどその中身が空だったらどうなるでしょうか。こういうときは何も表示されていないように見えます。つまり空のグリフが入ったフォントをWebフォントに使うと、見えない文字ができてしまいます。
こういうフォントについて適切に対策しないと読めない文字が生まれて、訪問者を困らせてしまうことになるでしょう。これはユーザビリティが低く、避けるべきです。
解決
フォントの改変が許されている場合は空のグリフを削除するのがいちばんよいです。ブラウザの互換性の問題もなく、ファイルサイズも減ります。Homebrewでfontforgeをインストールして、Pythonのスクリプティングインターフェースを用いてフォントを操作しましょう。
brew install fontforge —with-x
こういう感じでインストールします。--with-x
オプションを使うとX11でGUIが使えるからちょっと便利。
#!/usr/bin/env python # -*- coding: utf-8 -*- import fontforge import argparse def remove_empty_glyphs(input, output): font = fontforge.open(input) code_points = [] for glyph in font.glyphs(): # 実際には glyph のデータが空になっていることがある if glyph.left_side_bearing != 0.0 and glyph.right_side_bearing != 0.0: pass else: font.removeGlyph(glyph) font.generate(output) font.close() if __name__ == '__main__': parser = argparse.ArgumentParser(description='Make unicode-range from font.') parser.add_argument('input', nargs=1, help='path to input font file') parser.add_argument('output', nargs=1, help='path to output font file') args = parser.parse_args() input_file_path = args.input[0] output_file_path = args.output[0] remove_empty_glyphs(input_file_path, output_file_path)
Pythonのスクリプトはこういう感じで
python remove-empty-glyphs.py INPUT_FONT_FILE OUTPUT_FONT_FILE
みたいな感じで使います。拡張子に応じて保存されるフォントのフォーマットが変わって便利。
フォントの改変ができない場合は、ブラウザの互換性問題がありつつもunicode-range
を設定しておくのがよさそうです。@font-face {}
にunicode-range: U+XXX-XXX, U+XXX;
のようなかたちで指定します。unicode-range
をフォントの情報から生成するスクリプトを用意しました。
#!/usr/bin/env python # -*- coding: utf-8 -*- import fontforge import argparse def _code_points_from_font(filepath): font = fontforge.open(filepath) code_points = [] for glyph in font.glyphs(): point = glyph.unicode # unicode が見つからないとき -1 # 実際には glyph のデータが空になっていることがあるから side_bearing が 0.0 のものを除く if point != -1 and glyph.left_side_bearing != 0.0 and glyph.right_side_bearing != 0.0: code_points.append(point) return sorted(code_points) def _code_ranges_from_code_points(code_points): code_ranges = [] last_point = code_points[0] code_ranges.append([last_point]) for point in code_points: if point - last_point > 1 and point != last_point: # 連続していない code_ranges[-1].append(last_point) code_ranges.append([point]) else: # 連続している pass last_point = point return code_ranges def unicode_range_from_font(filepath): code_points = _code_points_from_font(filepath) code_ranges = _code_ranges_from_code_points(code_points) unicode_ranges = [] for code_range in code_ranges: if len(code_range) > 1: unicode_ranges.append('U+{0:X}-{1:X}'.format(code_range[0], code_range[-1])) elif len(code_range) == 1: unicode_ranges.append('U+{0:X}'.format(code_range[0])) unicode_range = ', '.join(unicode_ranges) unicode_range = 'unicode-range: {0};'.format(unicode_range) return unicode_range if __name__ == '__main__': parser = argparse.ArgumentParser(description='Make unicode-range from font.') parser.add_argument('font', nargs=1, help='path to font file') filepath = parser.parse_args().font[0] unicode_range = unicode_range_from_font(filepath) print unicode_range
これを用いて
python unicode-range-maker.py FONT_FILE | pbcopy
のようにするとクリップボードにunicode-range
がコピーされます。これをCSSに書いておきましょう。
@font-face { font-family: "name"; src: local("path"); unicode-range: ...; }
第一水準漢字までしか表示できないと「櫻井翔」が「 井 」みたいになってしまいますから、対応必至ですね。