シーザー暗号を作る文字フォントを作る

この教材では、普段何気なく使っている「文字フォントファイル」の構造を解き明かし、Pythonを使って文字の対応関係を書き換えることで、「Aと打つとBが表示される」という不思議なシーザー暗号フォントを自作します。


1.文字フォントファイルとは何か?

私たちがパソコンやスマホで文字を入力すると、画面に美しい文字が表示されます。この「文字の見た目」を提供しているのがフォントファイル(拡張子が .ttf.otf のもの)です。

フォントファイルの中身は、単なる画像データの集まりではありません。主に以下の2つの要素がセットになった**「巨大なデータベース(データベーステーブルの集まり)」**です。

  1. グリフ(Glyph): 文字の「形(アウトライン)」そのもののデータ。ベジェ曲線という数学的な数式で線の引き方が記録されています。
  2. テーブル(Tables): フォントのデータ(フォント名、著作権情報、文字の割り当てなど)を管理する各種の表。

フォントファイルはバイナリデータ(コンピュータしか読めないデータ)で固められているため、通常はそのまま編集できませんが、プログラムを使うことでこのデータベースを直接書き換えることができます。


2.Pythonでフォントを編集する準備と基本

Pythonでフォントファイルを操作するには、世界標準のライブラリである FontTools を使用します。まずは環境を整え、フォントファイルをプログラムで読み込む基本を学びましょう。

準備:ライブラリのインストール

コマンドライン(ターミナルやコマンドプロンプト)で以下のコマンドを実行します。

pip install fonttools

基本プログラム:フォントを読み込んで保存する

まずは、既存のフォントファイルを読み込み、何もせず別名で保存するだけの「基本の骨組み」を確認します。

from fonttools.ttLib import TTFont

# 既存 of フォントファイルを読み込む(同じフォルダに sample.ttf を用意してください)
font = TTFont("sample.ttf")

# フォントに特別な処理を施す(中身の書き換えはここに記述します)
print("フォントファイルを正常に読み込みました。")

# 新しい名前でフォントファイルを保存する
font.save("output_sample.ttf")
print("フォントファイルを保存しました。")

3.文字コードとグリフの対応関係(cmap)を解析しよう

キーボードで「A」を押したとき、なぜ画面に「Aの形」が出るのでしょうか? それを管理しているのが、フォントの内部にある cmap(シーマップ / Character Map) というテーブルです。

文字コードとグリフの関係

  • 文字コード(Unicodeなど): コンピュータが文字を識別するための背番号(例:大文字の「A」は 65 または 0x0041)。
  • グリフ名(Glyph Name): フォント内で「デザインデータ」につけられた名前(例:Auni0041 など)。

cmap は、「背番号 65 が来たら、A という名前の形を表示しなさい」 という仲介役(辞書)の役割を果たしています。

解析プログラム:フォントの対応関係をのぞいてみよう

実際にフォントファイルの中身を解析し、文字コードとグリフ名がどう結びついているかを確認してみましょう。

from fonttools.ttLib import TTFont

font = TTFont("sample.ttf")

# フォント内の cmap テーブルを取得
cmap_table = font['cmap']

# 最も標準的なUnicode対応表(サブテーブル)を見つける
unicode_table = None
for table in cmap_table.tables:
    if table.isUnicode():
        unicode_table = table.cmap
        break

if unicode_table:
    print("--- 文字コード と グリフ名 の対応関係(一部抜粋) ---")
    # 最初の20個だけ表示してみる
    count = 0
    for code, glyph_name in unicode_table.items():
        # code(数値)を実際の文字に変換して表示
        char = chr(code) if code > 32 else "[特殊文字]"
        print(f"文字: {char} (Unicode: {code}) ---> グリフ名: {glyph_name}")
        
        count += 1
        if count >= 20:
            break
else:
    print("Unicode対応表が見つかりませんでした。")

【学習のポイント】 このプログラムを実行すると、文字: A (Unicode: 65) ---> グリフ名: A のようなペアがずらりと並びます。このペアを意図的に「狂わせる」のが次のステップです。


4.「シーザー暗号作成フォント」を実装する

仕組みがわかったところで、いよいよ本番です。 A の文字コード(65)のときに、B のグリフ名が呼び出されるように cmap を書き換えます。アルファベット26文字が綺麗にローテーションするように計算(余り算 %)を使いましょう。

暗号フォント作成プログラム

from fonttools.ttLib import TTFont

def create_caesar_font(input_path, output_path, shift=1):
    font = TTFont(input_path)
    cmap_table = font['cmap']
    
    for table in cmap_table.tables:
        if not table.isUnicode():
            continue
            
        # 現在の「文字コード -> グリフ名」の対応表をコピー(参照用にキープ)
        original_cmap = dict(table.cmap)
        
        for code in original_cmap.keys():
            # 大文字 A(65) 〜 Z(90) の処理
            if 65 <= code <= 90:
                # shift分だけ「手前」の文字コードのグリフを持ってくることで、
                # 入力した時に「後ろ」の文字が表示されるようにする
                shifted_code = 65 + (code - 65 - shift) % 26
                if shifted_code in original_cmap:
                    table.cmap[code] = original_cmap[shifted_code]
                    
            # 小文字 a(97) 〜 z(122) の処理
            elif 97 <= code <= 122:
                shifted_code = 97 + (code - 97 - shift) % 26
                if shifted_code in original_cmap:
                    table.cmap[code] = original_cmap[shifted_code]

    # 保存する
    font.save(output_path)
    print(f"シーザー暗号フォント(ずらし量: {shift})を作成しました!")

# 実行(sample.ttf を読み込んで、caesar_font.ttf を出力)
create_caesar_font("sample.ttf", "caesar_font.ttf", shift=1)

実験してみよう!

  1. 作成された caesar_font.ttf をパソコンにインストールするか、フォントプレビューで開いてみてください。
  2. メモ帳などのテキストエディタを開き、フォントをこの暗号フォントに切り替えます。
  3. キーボードで HELLO と打ち込んでみましょう。画面には IFMMP (それぞれ1文字ずれた文字)と表示されるはずです!

5.ステップアップ!発展課題

基本が完成したら、さらにプログラムを改造してフォントの理解を深めてみましょう。

課題1:ずらす数を自由に変えられるようにしよう

現在のプログラムは1文字ずらすだけですが、関数の引数 shift=3 にしたときに3文字ずれる(A➔D、B➔Eになる)ようにコードが正しく動くか検証してください。また、shift=-1(逆方向にずらすデコードフォント)を作ってみましょう。

課題2:数字(0〜9)もループさせてみよう

アルファベットだけでなく、数字の 0(文字コード48)から 9(文字コード57)が入力されたときも、1つ後ろの数字が表示され、9 のときは 0 に戻るような処理をコードに追加してみましょう。

課題3:【高難易度】日本語(ひらがな)の暗号化に挑戦

日本の文字コードの並びを利用して、「あ」のキーを押したら「い」、「ん」のキーを押したら「あ」が表示されるような「ひらがな版シーザー暗号フォント」を作ってみましょう。

  • ヒント: ひらがなのUnicodeの開始位置は 0x3041(ぁ)から 0x3093(ん)の付近です。まずは「3」の解析プログラムを使って、ひらがなの文字コードがどう並んでいるか調べてみましょう。