PDFファイルに直接書き込む前に知っておくべき基礎知識
PDFは「静的なページレイアウトを記述したデータ形式」として設計されており、従来のワープロと異なり「ページを描画して終わり」という性質があります。そのため、文字を上書きしたり新たに挿入したりするには、元の構造を理解し上書き可能な位置を特定できる環境が必要です。この記事では、GUIのPDF編集ツールを使わずに、スクリプトやCLIツールの力でPDFに直接書き込む方法を実践的に解説します。
1. PDFの構造をざっくり見る
PDFは「オブジェクト → ページ → 文字列/ストリーム」の階層で構成されています。
- オブジェクト
- 直列化されたデータ(整数、実数、名前、文字列、配列、ディクショナリ、ストリーム)
- ページ
- 1ページは
Pageオブジェクトで表され、辞書内にテキストや図形を描画するリソース(フォント、イメージ)を参照します。
- 1ページは
- ストリーム
- テキストは Content Stream(
BT/.../ETで囲まれた文字列)という文字列の「描画命令」として保存されているので、直接文字列を埋め込み直すと、フォントを再解釈する必要があります。
- テキストは Content Stream(
これを踏まえて「既存テキストを上書き」するとは、実質的に対象ページのストリームを読み取り、指定位置に新しい描画命令を挿入する作業です。
2. 文字を挿入するための主なツール群
| ツール | 特徴 | 主要言語 | 代表的なコマンド / コード |
|---|---|---|---|
| PDFtk | シンプルなCLI。ページ結合・分割、パスワード解除が主。 | C++ | pdftk a.pdf cat 1-end output b.pdf |
| qpdf | オブジェクトレベルで編集可能。ページ入れ替えや再構成が得意。 | C++ | qpdf --pages in.pdf 1-z -- out.pdf |
| PyMuPDF (fitz) | Python から PDFを読み書きできる。描画命令を直接追加。 | Python | import fitz\n doc = fitz.open('file.pdf')\n page = doc[0]\n page.insert_text((100,100), "Hello")\n doc.save('out.pdf') |
| pypdf / PyPDF2 | PDF結合、分割、ページ抽出が簡単。テキストは抽出のみ。 | Python | PdfReader, PdfWriter |
| pdfrw | 低レベルでストリーム編集が可能。 | Python | Template機能でテキストを挿入 |
| PDFBox | Java ベース。低レベルAPIと高レベルAPI両方。 | Java | PDDocument, PDPageContentStream |
GUIツールと違い、CLI・ライブラリは「どこに何を書き込むか」を自分で指定します。以下では特に PyMuPDF と qpdf を中心に実装例を示します。
3. PyMuPDF(fitz)でテキストを追加する
PyMuPDF は fitz パッケージとしてインストールでき、描画命令を直接書き込むことが可能です。
3.1 インストール
pip install PyMuPDF
3.2 基本的なテキスト挿入
import fitz
# PDF を読み込む
doc = fitz.open('sample.pdf')
# 1ページ目(インデックスは 0 から)
page = doc[0]
# 位置 (ポイント単位)、フォントサイズ、色は自由に設定
text = "こんにちは、PDFへ書き込み!"
rect = fitz.Rect(50, 100, 300, 120) # 左下から右上までの枠
page.insert_textbox(rect, text, fontsize=12, fontname="helv", align=fitz.TEXT_ALIGN_LEFT, color=(0,0,0))
# 保存
doc.save('sample_modified.pdf')
insert_textboxは矩形領域内に文字列を描画。fontnameはPDFに埋め込まれたフォントを指定。helvは Helvetica(多くのPDFで埋め込まれている)。- 文字列の位置は
Point(50, 100)で座標を指定。左下から測ります。
3.3 既存テキストを上書きする(上からクリップ)
既存のテキストを「消した後」で新しい文字列を置くには、「クリップ」操作と描画命令を組み合わせます。
rect = fitz.Rect(100, 200, 400, 250) # 既存テキスト領域を測定
# クリップ - 既存文字を消す
page.insert_textbox(rect, "", overlay=False, fontsize=0)
# 上書きテキスト
page.insert_text((rect.x0, rect.y1 - 5), "上書きテキスト", fontsize=12, fontname="helv")
overlay=Falseとすることで、領域内の描画命令をクリアし、新規描画を上書きできます。
4. qpdf でページ単位の書き込み(ページ差し替え)
qpdf は低レベルでページ構造を操作可能。書き込みには「ページ差し替え」を使って、既存ページを改変した別ページで置き換えます。
4.1 qpdf のインストール
Ubuntu 18.04 以降なら apt、macOS なら brew で入れます。
# Ubuntu
sudo apt install qpdf
# macOS
brew install qpdf
4.2 1ページ目を書き換えるフロー
- 元の PDF を PyMuPDF でテキストを追加したサブPDFを作成
- qpdf で元の1ページを差し替え
# 1. PyMuPDF でテキスト追加(出力:tmp_page.pdf)
python - <<'PY'
import fitz
doc = fitz.open('sample.pdf')
page = doc[0]
page.insert_text((50, 100), "qpdf で差し替えるテキスト", fontsize=12)
doc.save('tmp_page.pdf')
PY
# 2. qpdf で差し替え
qpdf in.pdf --replace-image=1=tmp_page.pdf --output out.pdf
--replace-imageはオブジェクト番号を指定して差し替えるので、適切なオブジェクト番号を調べてください。qpdfの--pagesオプションを使い、ページの集合を再構成することで差し替えを実現できます。
5. 画像を挿入してテキストと併用する方法
テキストだけでなく画像を配置する場合、PyMuPDF の insert_image を使います。
img_path = 'logo.png'
rect = fitz.Rect(50, 400, 150, 500) # 画像の表示矩形
page.insert_image(rect, filename=img_path, keep_proportion=True)
keep_proportion=Trueにすると画像の縦横比を維持。- 画像は PDF 内に XObject として埋め込まれます。
6. 既存テキストを取得して編集する手順
「上書き」でなく「文字列を編集」したい場合は、元の文字列を抽出し、編集後に再挿入する手順が基本です。
# 文字列抽出
text = page.get_text("text")
# 編集(例: 文字列中の "旧文字" を "新文字" に置換)
new_text = text.replace("旧文字", "新文字")
# クリア + 新テキスト
page.insert_textbox(rect, new_text, fontsize=12, fontname="helv")
ただし、元のフォントや配置が失われるため、元と同じレイアウトを保つには詳細な座標管理が必要です。
7. 大規模なPDFに対するバッチ処理
大量の PDF に対して同じ編集を行いたい場合、Python スクリプトでループを作成し、fitz でページを処理、qpdf で差し替えを自動化します。
import pathlib, fitz, subprocess
src_dir = pathlib.Path('pdfs')
out_dir = pathlib.Path('output')
out_dir.mkdir(exist_ok=True)
for pdf_path in src_dir.glob('*.pdf'):
doc = fitz.open(pdf_path)
for i, page in enumerate(doc):
# テキスト追加(例: ページ番号)
page.insert_text((50, 10), f"ページ {i+1}", fontsize=10)
tmp_path = out_dir / f"tmp_{pdf_path.name}"
doc.save(tmp_path)
# qpdf で差し替え:ここでは簡易化のために全書き替え
subprocess.run(['qpdf', str(tmp_path), '--output', str(out_dir / pdf_path.name)])
8. フォント埋め込みと文字化け防止
PDF に書き込む際は フォント埋め込み を行わないと受信者側で文字化けする恐れがあります。PyMuPDF では ensure_outline=True 等のパラメータはありませんが、Page.insert_textbox で使用するフォント名が PDF で埋め込まれている場合は安全です。
- もし外部フォントを埋め込みたい場合は、
fitz.open()でfiletype="pdf"とし、insert_textの引数にフォントパスを渡す必要があります。
9. PDF のセキュリティ設定を無視するケース
暗号化された PDF では「読み取り権限なし」になると文字列の書き込みもブロックされます。fitz では PdfError が発生しますので、次のように解除します。
doc = fitz.open('encrypted.pdf')
if doc.is_encrypted:
doc.authenticate('mypassword')
# 以降通常操作
10. よくある質問とトラブルシューティング
| Q | A |
|---|---|
| Q1. テキストが上手く表示されない | フォントが埋め込まれていない、もしくはサイズ/位置がずれている可能性があります。page.get_text("dict") で文字列の位置情報を取得し、正しい座標に再配置してください。 |
| Q2. 既存テキストを消したいが残ってしまう | insert_textbox で overlay=False を指定し、クリップ領域で "" を描画します。さらにページのオブジェクトリストを確認し、不要な描画命令を削除しましょう。 |
| Q3. 画像を入れたあとにテキストが被る | 画像の矩形 (rect) とテキストの座標を重ねないようにします。rect.y0 とテキストの y1 を調整してください。 |
| Q4. 複数ページで同じ位置に同じ文字列を書き込みたい | ループで for i, page in enumerate(doc): page.insert_text((x, y), "共通文字列") とすれば簡単です。ただしページレイアウトが異なる場合は個別に座標を再計算する必要があります。 |
11. まとめ
- PDFは描画命令で構成しているため、文字を「上書き」したい場合は既存ストリームを読み取り、書き換える必要があります。
- GUI 無しでスクリプト・CLI だけで済ませるなら PyMuPDF(Python)と qpdf(CLI)が特に汎用性が高い。
- 画像やフォントの埋め込みも手軽に扱えるよう、
insert_imageとフォント名の確認を忘れずに。 - 大規模業務では バッチスクリプト や CI/CD パイプライン に組み込むことで自動化できます。
PDF の構造と描画命令を基礎に、これらのツールで「PDF編集ツールなし」でも任意の文字・画像を書き込むことが可能です。まずは簡単なサンプルに挑戦し、徐々に高度なレイアウト調整へと拡張してみてください。


コメント