PDF座標取得のススメ:初心者でも実践できる簡単手順

はじめに

PDF は「ページ」や「文字」、そして「画像」など多くの要素をレイアウトベースで保持しています。
Web 上で閲覧・編集・検索する際に「ある文字が 10‑20 cm の位置にある」などの情報を使いたいとき、
PDF の内部座標を知ることが不可欠です。
しかし、PDF は固定レイアウトで描画されるため、開発者は「ページ上の座標」や「文字のバウンディングボックス」をどう取得すれば良いか迷いがちです。
本記事では初心者でも 手軽に PDF の座標情報を取り出す手順 を、
Python のライブラリを使った実例中心に紹介します。


PDF の座標体系を理解する

PDF の座標は ポイント(pt) で表現され、
1pt=1/72 インチ(約0.35 mm)です。
座標系は左上が (0, 0) で、X が右方向に増え、Y が下方向に増える「上基準座標系」が基本です。
しかし、ページ単位の メディアボックス(MediaBox)トリミングボックス(TrimBox) のサイズが異なると座標は変わるため、
座標を正確に扱うためにはまず ページサイズ を取得する必要があります。


何故座標情報が必要なのか?

  1. テキスト検索とハイライト
    キーワードを検索し、該当箇所をハイライト表示する際に、
    文字列が描画されている矩形領域を取得して色を塗る必要があります。

  2. 図形や画像の位置取り
    PDF から図形認識を行い、画像を別ファイルに切り出す際に
    画像の座標とサイズを取得する必要があります。

  3. レイアウト解析
    文章の列の区分や段落の位置を解析して、
    版面設計情報を抽出する処理。

  4. データの書き込み
    既存の PDF に追加テキストを書き込む際に、
    既存テキストと衝突しない位置を計算する。


Python で PDF の座標情報を取得する環境構築

以下の手順は、ローカル環境(Windows / macOS / Linux)で動作します。
インストール済みでない Python(3.9 以降)と pip を前提としています。

必要なライブラリ

ライブラリ 役割 インストール例
PyMuPDF PDF 全般の取得・描画 pip install pymupdf
pdfplumber テキストと座標の取得 pip install pdfplumber
pikepdf 低レベルの PDF 操作 pip install pikepdf

備考
それぞれのライブラリは重複する機能もあるので、
目的に合わせて使い分けます。今回の例では PyMuPDFfitz というモジュール名)と pdfplumber を併用します。

# 端末で実行
python -m pip install --upgrade pip
pip install pymupdf pdfplumber pikepdf

1. PyMuPDF(fitz)でページサイズと全体座標を取得する

PyMuPDF は高速な PDF 読み込みと描画に特化しており、
ページサイズ、メディアボックス、トリミングボックスを簡単に取得できます。

import fitz  # PyMuPDF のモジュール名

pdf_path = "sample.pdf"
doc = fitz.open(pdf_path)

print(f"総ページ数: {len(doc)}")

# 1 ページ目を取得
page = doc[0]

# メディアボックス(ページ全体)
media_box = page.MediaBox
print(f"MediaBox (x0, y0, x1, y1): {media_box}")

# トリミングボックス(実際に表示される領域)
trim_box = page.TrimBox
print(f"TrimBox (x0, y0, x1, y1): {trim_box}")

# ページ幅・高さ(ポイント単位)
width_pt, height_pt = page.rect.width, page.rect.height
print(f"幅: {width_pt:.2f}pt, 高さ: {height_pt:.2f}pt")

ポイント→ミリメートル換算

1~\text{pt} = \frac{25.4}{72}~\text{mm} \approx 0.3527778~\text{mm}

したがって、幅を mm で確認したい場合は width_pt * 0.3527778 とします。


2. pdfplumber で文字列とバウンディングボックスを取得

pdfplumber はテキスト抽出に特化し、文字ごとに座標情報を返します。
x0, top, x1, bottom を使って矩形領域を把握できます。

import pdfplumber

with pdfplumber.open(pdf_path) as pdf:
    page = pdf.pages[0]  # 1 ページ目

    # すべての文字情報を取得
    chars = page.chars
    print(f"取得した文字数: {len(chars)}")

    # 例:最初の 10 文字を表示
    for i, char in enumerate(chars[:10], start=1):
        print(f"{i:2d}: '{char['text']}' -> "
              f"({char['x0']:.2f}, {char['top']:.2f}) "
              f"to ({char['x1']:.2f}, {char['bottom']:.2f})")

char の主なキー

キー 説明
text 文字
x0 左上の X 座標
top 左上の Y 座標
x1 右下の X 座標
bottom 右下の Y 座標
size フォントサイズ(ポイント)
line_num 行番号
page_number ページ番号

pdfplumber行単位 で検索したり、列幅 を計算して列レイアウトを判定することも簡単です。


3. 文字列検索と座標マッピング

検索キーワードが存在する位置の座標を取得する例です。
単語レベル で検索し、単語のバウンディングボックスを算出します。

import pdfplumber

keyword = "Python"

with pdfplumber.open(pdf_path) as pdf:
    page = pdf.pages[0]
    words = page.extract_words()

    # キーワードが含まれる単語を探す
    for w in words:
        if keyword.lower() in w["text"].lower():
            print(f"見つかりました: '{w['text']}'")
            print(f"  (x0, top) -> ({w['x0']:.2f}, {w['top']:.2f}) "
                  f"  (x1, bottom) -> ({w['x1']:.2f}, {w['bottom']:.2f})")

補足
extract_words() は複数の文字を結合して単語を生成し、
単語のバウンディングボックスを算出します。
文字ごとに精度が必要な場合は page.chars を直接操作します。


4. PDF から図形(矩形・線)の座標を取得

PyMuPDF には図形解析機能があります。
page.get_drawings() で円・矩形・線などの描画情報を取得できます。

drawings = page.get_drawings()
print(f"描画オブジェクト数: {len(drawings)}")

for i, d in enumerate(drawings, start=1):
    print(f"{i:2d}: type={d['type']} -> "
          f"points={d['points']}")

type の代表値

意味
rect 矩形
path 線・複合パス
image 画像
text 文字列

重要
PDF では図形とテキストが同じ座標系で保存されます。
座標系が ページ上端を原点 とし、Y が下方向に増える点を覚えてください。


5. 座標を使った「テキストハイライト」例

検索したキーワードの位置に矩形を描画し、ハイライト色を付ける。
これにより、検索結果を視覚的に確認できます。

import fitz
import pdfplumber
from pathlib import Path

doc = fitz.open(pdf_path)
page = doc[0]

# 検索キーワード
keyword = "Python"

with pdfplumber.open(pdf_path) as pdf:
    page_py = pdf.pages[0]
    words = page_py.extract_words()

    for w in words:
        if keyword.lower() in w["text"].lower():
            # 位置情報
            rect = fitz.Rect(w["x0"], w["top"], w["x1"], w["bottom"])
            # ハイライト色(赤)
            page.draw_rect(rect, color=(1, 0, 0), fill=(1, 0.8, 0.8), width=0)

# 保存
output_path = Path("highlighted.pdf")
doc.save(output_path)
print(f"ハイライト付き PDF を保存: {output_path}")

補足

  • draw_rectfill塗りつぶし を、 color枠線 を設定します。
  • width=0 にすると枠線は描画されません。
  • すべてのページに対して同様の処理をループすれば、全文検索とハイライトを実装できます。

6. 座標を活用した画像切り出し

PDF 内の画像を抽出し、位置とサイズ情報を取得する。
PyMuPDFpage.get_images() で画像リストを返し、
それぞれのバウンディングボックスを取得できます。

import fitz
import io
from PIL import Image

doc = fitz.open(pdf_path)
page = doc[0]
images = page.get_images(full=True)

print(f"画像数: {len(images)}")

for img_index, img in enumerate(images, start=1):
    xref = img[0]
    base_image = doc.extract_image(xref)
    image_bytes = base_image["image"]
    image_ext = base_image["ext"]

    # Pillow で画像を開く
    pil_img = Image.open(io.BytesIO(image_bytes))

    # バウンディングボックスを取得(画像が配置されている矩形)
    # page.get_image_bbox(xref) は bbox を返す
    bbox = page.get_image_bbox(xref)

    print(f"Image {img_index}: bbox={bbox} , size={pil_img.size}, ext={image_ext}")

    # 例: そのままファイル化
    pil_img.save(f"image_{img_index}.{image_ext}")

ポイント
画像は PDF の XREF システムで参照されています。
xref を使って extract_image でバイナリを取り出します。
画像が重なっている場合も同様に bbox で矩形座標を把握できるので、
位置情報付きで 画像検索 を行う基盤が作れます。


7. ベース・トリミング・メディアボックスの違いで座標が変わるケース

PDF のページの座標系は、

  1. メディアボックス(ページ全体)
  2. トリミングボックス(印刷領域)
  3. 出力用ボックス(表示領域)

これらが同じでない場合、座標がずれます。
実際のレイアウトを正確に把握したいときは、以下の手順で対処します。

page = doc[0]
print("MediaBox:", page.MediaBox)
print("TrimBox :", page.TrimBox)
print("BleedBox:", page.BleedBox)
print("ArtBox  :", page.ArtBox)
print("CropBox :", page.CropBox)  # 実際に表示される領域

実務上
ブックレットやカバー設計では CropBox が実際に印刷される領域です。
それ以外は余白やトリミング余白として扱います。

座標を取得する時は fitz.Rectpdfplumber が返す座標が MediaBox を基点にしている事を前提に計算します。
トリミングボックスを基準にしたい場合は、pdfplumberlayout を指定し、
座標を手動でトリミングオフセット分だけ減算してください。


8. まとめ:座標取得で実現できる 5 つの典型ユースケース

実装例 概要 使用ライブラリ
テキストハイライト 検索キーワードを矩形で塗りつぶし PyMuPDF
図形認識 画像枠を取得し位置を記録 PyMuPDF
レイアウト分析 行間・列幅を計算して版面を推定 pdfplumber
画像切り出し 位置情報付きで図像を抜き出し PyMuPDF
注釈埋め込み テキスト・図形に注釈(コメント)を追加 PyMuPDF

実装のコツ

  1. 座標単位 を統一(すべてポイントに統一)
  2. ページ番号 を必ず保持(ページごとの座標が別物になる)
  3. 座標系のオフセット を確認(トリミング/クロップ)
  4. テキストブロック を文字単位で取得してもいいし、行単位で取得しても大丈夫
  5. デバッグ時は draw_rect で矩形を可視化すると意図した位置か確認しやすい

9. よくある質問(FAQ)

Q1. PDF が複数ページある場合、すべてのページから同じキーワードを取得したい。
A1. for page in doc: でループし、各ページで同じ extract_words() を呼び出せば簡単です。

for pnum, page in enumerate(doc, start=1):
    with pdfplumber.open(pdf_path) as pdf:
        words = pdf.pages[pnum-1].extract_words()
        # 以降同様に処理

Q2. PDF 内のテキストが画像化されている場合は座標取得できない?
A2. 画像化されているテキストは OCR が必要です。
PIL、pytesseract などで画像を抽出し、OCR で文字位置を推定します。

Q3. 座標系が正しそうに見えるが、ハイライトがずれるケースは。
A3. まずは page.rect(ページ上端原点)とハイライトした矩形を draw_rect で描画して画面上に表示し、
印刷時のトリミングオフセットを確認してください。

Q3. 座標取得の結果を JSON 形式で保存したい。
A3. 取得した rect, word, image などを Python の辞書にまとめ、

import json
json.dump(result_dict, open("layout.json", "w"), ensure_ascii=False, indent=4)

して JSON ファイルに吐けばすぐに外部ツールで利用できます。


10. さらに深掘りしたい人へ

  • PDFMiner.six はより低レイヤーで座標に対応します。
  • poppler + cpp‑PDF で高速化したいデータパイプラインを構築。
  • PyMuPDF (fitz)page.get_text("dict") でテキストの 位置フォント を完全に取得できます。
  • Apache PDFBox (Java) も同等に座標情報を抽出可能。

ぜひ、これらのサンプルをベースに自社の処理フローに合わせてカスタマイズしてください。


今回の説明は、
「PDF が持つ座標情報を理解し取り扱い方を学び、
具体的な処理サンプルを通じて実務での活用例を身近に紹介」
が目的です。
ぜひコードをコピー&ペーストし、サンプリングファイルで動作確認しながら、
あなたの PDF 解析プロジェクトに活かしてみてください。

コメント