レッスン105
シーザー暗号を作る文字フォントを作る⌗
この教材では、普段何気なく使っている「文字フォントファイル」の構造を解き明かし、Pythonを使って文字の対応関係を書き換えることで、「Aと打つとBが表示される」という不思議なシーザー暗号フォントを自作します。
1.文字フォントファイルとは何か?⌗
私たちがパソコンやスマホで文字を入力すると、画面に美しい文字が表示されます。この「文字の見た目」を提供しているのがフォントファイル(拡張子が .ttf や .otf のもの)です。
フォントファイルの中身は、単なる画像データの集まりではありません。主に以下の2つの要素がセットになった**「巨大なデータベース(データベーステーブルの集まり)」**です。
- グリフ(Glyph): 文字の「形(アウトライン)」そのもののデータ。ベジェ曲線という数学的な数式で線の引き方が記録されています。
- テーブル(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): フォント内で「デザインデータ」につけられた名前(例:
Aやuni0041など)。
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)
実験してみよう!⌗
- 作成された
caesar_font.ttfをパソコンにインストールするか、フォントプレビューで開いてみてください。 - メモ帳などのテキストエディタを開き、フォントをこの暗号フォントに切り替えます。
- キーボードで
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」の解析プログラムを使って、ひらがなの文字コードがどう並んでいるか調べてみましょう。