フォントを同梱したデジタル商品、OFL.txtだけで足りるかを3つのZIPで確かめた

AIにテンプレートの見た目を整えさせると、最後に選んだフォントをZIPへ入れたくなります。見本のファイル名がNotoSansJP-Regular.ttfなら、横にOFL.txtを置けば済むようにも見えます。しかし、空のOFL.txtでも検査を通してしまうなら、置いた意味がありません。
今回は、販売するデジタル商品を想定した合成見本で確かめました。実在のフォントや商品ファイルは配布していません。フォント風の空ファイルを入れた3つのZIPに対し、ライセンス文書の有無と、著作権表示とSIL Open Font License 1.1の文言を調べます。
ファイル名だけでは、配布条件まで分かりません
SIL Open Font License 1.1は、フォントをソフトウェアとまとめて再配布・販売できるとしています。一方で、各コピーには著作権表示とライセンスを含める条件があります。ですから、商品ZIPにフォントファイルを入れるなら、ライセンス文書の存在を販売前の確認項目にする価値があります。見つけるのは、配布前の小さな穴。
ただし、これは法的な判断を自動化する検査ではありません。改変したフォントの予約フォント名、個別の配布元が添える通知、販売ページの説明は別に確認します。ここで止めるのは、もっと手前の「ライセンス文書が無い」「中身が空に近い」という事故だけです。
Google FontsのNoto Sans JPは、日本語のひらがな、カタカナ、漢字を含むフォントです。AIが下書きしたPDFや画像テンプレートで日本語を扱うと、見た目の確認に意識が寄りがちです。個人で扱う小さな商品ほど、同梱物の説明を機械的に見直せる形にしておくほうが後で追えます。
今回の見本で決めた通過条件
ZIPの中に.ttfか.otfが1個以上あるとき、OFL.txtが少なくとも1個あり、その本文にCopyrightとSIL Open Font License, Version 1.1の両方があることを通過条件にしました。フォントを含むZIPだけが対象です。フォントを含まないZIPは入力ミスとして止めます。ここは、入力の境界。
| 用語 | この見本での意味 |
|---|---|
| OFL | SIL Open Font License。フォントソフトウェア用のライセンスです。 |
| フォントソフトウェア | ライセンス本文が対象とするフォントファイルや関連ファイルです。 |
| 著作権表示 | 権利者を示すCopyrightを含む記載です。 |
| 予約フォント名 | 改変版での利用に制限があり得る名称です。 |
| 配布コピー | 購入者へ渡すZIPのように、ライセンス条件を満たす必要がある単位です。 |
この条件は、ある特定のフォントが使えると証明しません。逆に、正しいライセンス文書が別名で入っている商品は止めます。判断の境界を狭くし、止まったZIPだけを人が開くための入口です。検査の役目は、ここまで。
[AIが整えたテンプレート商品]
|
v
[配布用ZIPを作る]
|
v
[.ttf / .otfを見つける] -- 0個 --> 入力エラー
|
v
[OFL.txtを探す] -- なし --> 終了コード1
|
v
[著作権表示とOFL 1.1を読む] -- 欠ける --> 終了コード1
|
v
通過
ZIPを読む小さな検査を作る
次の内容を$TMPDIR/check_ofl_bundle.pyとして保存しました。$TMPDIRは、その場で消してよい一時ディレクトリです。このスクリプトはフォントを解析せず、ZIP内の名前とテキストだけを読みます。対象は、配置と文書の中身だけ。
from __future__ import annotations
import sys
import zipfile
from pathlib import PurePosixPath
REQUIRED = "sil open font license, version 1.1"
def fail(message: str) -> int:
print(f"ERROR: {message}")
return 1
def main(path: str) -> int:
with zipfile.ZipFile(path) as archive:
names = [name for name in archive.namelist() if not name.endswith("/")]
fonts = [name for name in names if PurePosixPath(name).suffix.lower() in {".otf", ".ttf"}]
licenses = [name for name in names if PurePosixPath(name).name.lower() == "ofl.txt"]
if not fonts:
return fail("フォントファイルがありません")
if not licenses:
return fail(f"OFL.txtが見つかりません (font={fonts[0]})")
for name in licenses:
text = archive.read(name).decode("utf-8", errors="replace").lower()
if "copyright" in text and REQUIRED in text:
print(f"OK: font={len(fonts)} ofl={name}")
return 0
return fail("OFL.txtに著作権表示とOFL 1.1の文言がそろっていません")
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1]))
3つのZIPを同じ条件で通しました
空のフォント風ファイル1個と、必要な文言を含むfonts/OFL.txtを入れたgood.zipを作り、実行しました。これは実フォントの配布物ではありません。判定器が見る場所だけを再現した見本です。
python3 "$TMPDIR/check_ofl_bundle.py" "$TMPDIR/good.zip"
OK: font=1 ofl=fonts/OFL.txt
次に、OFL.txtを入れないmissing.zipを渡しました。フォント名を出して、終了コード1で止まりました。
python3 "$TMPDIR/check_ofl_bundle.py" "$TMPDIR/missing.zip"
echo $?
ERROR: OFL.txtが見つかりません (font=fonts/NotoSansJP-Regular.ttf)
1
最後はOFL.txtという名前だけを残し、中身を「確認中」にしたempty.zipです。名前の存在では通さず、同じく終了コード1になりました。
python3 "$TMPDIR/check_ofl_bundle.py" "$TMPDIR/empty.zip"
echo $?
ERROR: OFL.txtに著作権表示とOFL 1.1の文言がそろっていません
1
空のOFL.txtでも通る条件では、購入者に渡すファイルを確認したことにはなりません。ここで見る語は少ないですが、無いよりは強い境界になりました。
販売前に人が確認する部分を残す
この検査が通っても、フォントを同梱してよいと即断しません。配布元の実際のOFL.txtを開き、著作権表示、予約フォント名、改変の有無を見ます。販売ページには、どのフォントをどの形で含めるのかも書き分けます。
AIに生成させた画像やPDFへフォントが埋め込まれる場合も、同じ発想を使えます。ただし、埋め込み可否を調べる検査と、ZIPにフォントファイルを同梱するときの検査は別物です。前者は文書の中身、後者は配布物の条件を見ます。混ぜないことが大切です。
小さな商品では、販売作業を急ぐと「使った素材の説明」が最後に残ります。AIに任せた作業ほど、入力した素材と最終ZIPを人の記憶だけでつなげないほうが安全です。まずは配布物を止める条件を1つ決め、止まったときに元のライセンスを読む流れを作ります。


