販売用PDFに文字と重なるリンクや添付メモが残っていないか、見本4つで確かめる

販売用PDFに文字と重なるリンクや添付メモが残っていないか、見本4つで確かめる

「Reference guide」としか印刷されていないPDFがあります。本文にURLはありません。それでも、その文字の上には外部サイトへのリンクが重なっています。

ここで扱うのは、Pythonで作った合成見本4つです。AIが出力したPDFは使っていません。前回の販売用PDFの検査ではページ数とフォントを見ましたが、文字の裏にあるリンクや添付物は見ていませんでした。今回はその穴の話です。

見える文字の外に残るもの

PDFのリンクは、URLの文字列がなくても置けます。ページ上のクリックできる範囲に行き先を持たせる仕組みで、リンク注釈と呼ばれます。PyMuPDFのPage.get_links()はこれを辞書で返し、外部への行き先はuriに入ります。

もう1つは埋め込みファイルです。PDF本体に別のファイルを添付できて、本文を抜き出しても出てきません。Document.embfile_names()で名前を列挙できます。

どちらも、買い手の手元に届くもの。だから販売前に、存在だけは止めておきたい。リンク先が危険かどうかまでは、この検査では判定しません。

4つの見本を作る

リンク先は無効な例示ドメインexample.invalid、添付は架空の1行メモです。実際の商品や秘密情報は入れていません。作業は一時ディレクトリで行い、Python 3.11.15とPyMuPDF 1.28.2を使いました。記事のリポジトリには何も足していません。

一時ディレクトリに仮想環境を作り、PyMuPDFを入れます。

uv venv venv
uv pip install --python venv/bin/python pymupdf==1.28.2

次のコードをhibanas-pdf-maker.pyとして保存します。

hibanas-pdf-maker.py(25行)
from pathlib import Path
import pymupdf

out = Path(__file__).parent / "hibanas-pdf-lab"
out.mkdir(exist_ok=True)
for name, link, attached in (
    ("clean", False, False),
    ("link-only", True, False),
    ("attachment-only", False, True),
    ("both", True, True),
):
    doc = pymupdf.open()
    page = doc.new_page()
    page.insert_text((72, 80), "AI worksheet for sale")
    page.insert_text((72, 110), "Reference guide")
    if link:
        page.insert_link({"kind": pymupdf.LINK_URI,
                          "from": pymupdf.Rect(72, 94, 220, 120),
                          "uri": "https://example.invalid/staging"})
    if attached:
        doc.embfile_add("internal-note.txt", b"Synthetic draft note; not for distribution.\n")
    path = out / f"{name}.pdf"
    doc.save(path)
    doc.close()
    print(f"wrote {path.name}")

問題のないcleanのほかに、リンクだけ、添付だけ、両方の3つを作ります。本文は4つとも同じです。

リンクの範囲はRect(72, 94, 220, 120)で、ちょうど「Reference guide」の行を覆う位置です。画面で見ると、ただの見出しの文字にしか見えません。添付はembfile_add()で文書に付けるだけなので、ページの見た目は何も変わりません。見本として作りたかったのは、この「見た目では区別がつかない」状態です。

venv/bin/python hibanas-pdf-maker.py
wrote clean.pdf
wrote link-only.pdf
wrote attachment-only.pdf
wrote both.pdf

検査を1つずつ足す

検査は1つのスクリプトにまとめ、引数で範囲を切り替えます。textは本文の文字だけ、linksはリンク注釈も、allは埋め込みファイルまで見ます。hibanas-pdf-check.pyとして保存しました。

hibanas-pdf-check.py(18行)
import sys
import pymupdf

mode = sys.argv[1]
for path in sys.argv[2:]:
    with pymupdf.open(path) as doc:
        text = "\n".join(page.get_text() for page in doc)
        hits = []
        if "https://" in text:
            hits.append("visible URL")
        if mode in ("links", "all"):
            for page_no, page in enumerate(doc, 1):
                for link in page.get_links():
                    if link.get("kind") == pymupdf.LINK_URI:
                        hits.append(f"page={page_no} uri={link.get('uri')}")
        if mode == "all":
            hits.extend(f"embedded={name}" for name in doc.embfile_names())
    print(f"{path.rsplit('/', 1)[-1]}: {'FAIL' if hits else 'PASS'}", *hits, sep=" | ")

読みどころは3か所です。textでは、全ページの文字をつないでhttps://を探すだけ。linksでは、ページごとにget_links()を回し、種類がLINK_URIのものだけを拾います。ページ内の移動リンクはここでは数えません。allでは、ページの外にある添付の名前をembfile_names()で文書から取り出します。何か1つでも見つかればFAILとし、見つけた中身を同じ行に並べます。

まず文字だけです。

venv/bin/python hibanas-pdf-check.py text hibanas-pdf-lab/*.pdf
attachment-only.pdf: PASS
both.pdf: PASS
clean.pdf: PASS
link-only.pdf: PASS

4つともPASS。URLが印刷されていない以上、文字を探しても見つかりません。

次にリンク注釈を足します。

venv/bin/python hibanas-pdf-check.py links hibanas-pdf-lab/*.pdf
attachment-only.pdf: PASS
both.pdf: FAIL | page=1 uri=https://example.invalid/staging
clean.pdf: PASS
link-only.pdf: FAIL | page=1 uri=https://example.invalid/staging

リンク入りの2つは止まりました。ではattachment-only.pdfはどうか。まだ通ります。添付はページではなく文書全体に付くので、ページを見て回っても出てきません。

最後に埋め込みファイルです。

venv/bin/python hibanas-pdf-check.py all hibanas-pdf-lab/*.pdf
attachment-only.pdf: FAIL | embedded=internal-note.txt
both.pdf: FAIL | page=1 uri=https://example.invalid/staging | embedded=internal-note.txt
clean.pdf: PASS
link-only.pdf: FAIL | page=1 uri=https://example.invalid/staging

通ったのはclean.pdfだけ。ただし、自分で入れた3種類の不備を拾えたというだけで、どんなPDFでも見分けられるわけではありません。

止めたあと、どうするか

検出したリンクや添付は自動で消しません。購入者向けの正しい参考リンクや、意図して同梱した資料もありえるからです。名前と行き先を見て、不要なら制作元のデータから消して出力し直します。必要なものなら、納品仕様に書いておきます。たとえば「1ページ目の参考資料は公式ドキュメントへのリンク」「添付は記入例のCSVが1つ」のように、検査で出た行と照らせる形にしておくと、次の版でも同じ確認で済みます。

私がまだ決めきれていないのは、正当なリンクをどう通すかです。今はURIが1つでもあればFAILにしています。許可するドメインを並べる方法もありますが、その一覧の管理が新しい手間になります。販売前に毎回人が開いて確かめるほうが、結局は早いのかもしれません。

まだ見ていないものもあります。URI以外のリンク、ファイル添付注釈、JavaScript、フォームの動作、閲覧ソフトごとの表示の違いです。次は実際の販売用ワークシートの複製に同じ検査をかけます。

出典

この記事をシェア