PDF スキル
構造化された仕様からPDFを作成し、AcroFormフォーム(レイアウトのlintと視覚的なオーバーレイ付き)を構築・入力し、テキスト・表・メタデータを抽出し、ページの結合・分割・回転・透かし・スタンプを実行し、ページ画像をエクスポートし、メタデータと添付ファイルを管理し、暗号化・復号を行います — pypdf、reportlab、pdfplumberを使用します。2つの取り込み済み機能は references/ にあります(それらのタスクの前に対応するファイルを読んでください)。
- スキャンされた/画像のみのPDFとOCR(pymupdf高速パス、marker-pdf高品質パス、scripts/extract_pypymupdf.py + scripts/extract_marker.py):
references/ocr-extraction.md - 自然言語プロンプトによる既存PDF内のテキスト編集(nano-pdf CLI):
references/nano-pdf-editing.md
使用するタイミング
- レポート、請求書、複数ページのドキュメントをPDFとして生成する。
- JSON仕様から入力可能なAcroForm(テキスト/チェックボックス/ラジオ/ドロップダウン)を構築し、最初にレイアウトをlintする。
- PDFからテキスト、表(JSON/CSV)、メタデータ、またはフォームフィールドの値を抽出する。
- ページの結合、分割、回転、サブセット抽出、透かし、座標指定でのテキスト/画像のスタンプ、ブックマーク、圧縮を行う。
- 視覚的なレビューやOCRへの受け渡しのためにページをPNGとしてエクスポートする。ドキュメントメタデータの設定/クリア、ファイル添付の追加/抽出を行う。
- AcroFormフォームを入力またはフラット化する。パスワードで暗号化または復号する。
- スキャンされた/画像のみのPDFには使用しないでください(
references/ocr-extraction.mdを使用)。ピクセルパーフェクトなHTMLからPDFへのレンダリングには使用しないでください(ヘッドレスブラウザを使用)。
前提条件
- Python 3.10+ と
pypdf、reportlab、pdfplumber:python -m pip install pypdf reportlab pdfplumber - オプション: ページラスタライゼーション(
pdf_page_image.py、オーバーレイレンダリング)用:python -m pip install pypdfium2、またはPATH上のpopplerのpdftoppm。スクリプトは pypdfium2 → pdftoppm の順にフォールバックし、どちらも存在しない場合は{"rendered": false, "missing": [...]}(終了コード0)を報告します。 - 各ヘルパースクリプトは依存関係を遅延インポートでチェックし、欠落している場合はインストールのヒントを表示します。
実行方法
すべてのヘルパーは scripts/ にあり、argparse CLIです — terminal ツールで実行してください。すべてのスクリプトは --help をサポートしています。JSONは厳密にUTF-8で読み書きし、JSON結果をstdoutに出力し、失敗時は非ゼロで終了します。
python scripts/pdf_create.py spec.json -o out.pdf # JSON仕様からPDFをビルド
python scripts/pdf_make_form.py formspec.json -o form.pdf # JSON仕様から入力可能なAcroFormをビルド
python scripts/pdf_form_layout.py formspec.json # フォームレイアウトをビルド前にlint
python scripts/pdf_form_layout.py formspec.json --render-overlay boxes.png [--pdf form.pdf]
python scripts/pdf_read.py doc.pdf --text # ページごとのテキスト (JSON)
python scripts/pdf_read.py doc.pdf --tables --csv-dir t/ # 表をJSON + CSVファイルに出力
python scripts/pdf_read.py doc.pdf --meta # メタデータ、ページサイズ、暗号化/スキャンフラグ
python scripts/pdf_read.py form.pdf --fields # フォームフィールド: 名前、タイプ、値
python scripts/pdf_merge.py a.pdf b.pdf -o merged.pdf [--bookmarks]
python scripts/pdf_split.py doc.pdf --pages 1-3,7 -o part.pdf [--rotate 90]
python scripts/pdf_fill_form.py form.pdf --fields-json values.json -o filled.pdf [--flatten]
python scripts/pdf_secure.py doc.pdf --encrypt -o enc.pdf --user-password your-password
python scripts/pdf_secure.py enc.pdf --decrypt -o dec.pdf --password your-password
python scripts/pdf_watermark.py doc.pdf --stamp mark.pdf -o stamped.pdf [--under]
python scripts/pdf_stamp.py doc.pdf -o out.pdf --text "DRAFT" --x 150 --y 400 \
--font-size 60 --rotation 45 --opacity 0.3 --color "#cc0000" [--pages 1-3]
python scripts/pdf_stamp.py doc.pdf -o out.pdf --image sig.png --x 400 --y 60 --width 120
python scripts/pdf_page_image.py doc.pdf --pages 1-3 --dpi 150 --out-dir imgs/
python scripts/pdf_meta.py doc.pdf --set-meta --title "T" --author "A" -o out.pdf
python scripts/pdf_meta.py doc.pdf --attach data.csv -o out.pdf
python scripts/pdf_meta.py doc.pdf --list-attachments | --extract-attachments dir/
クイックリファレンス
| タスク | ツール | コマンド / API |
|---|---|---|
| ドキュメント作成(見出し、表、画像) | reportlab platypus | pdf_create.py spec.json -o out.pdf |
| 入力可能フォームのビルド | reportlab acroForm | pdf_make_form.py formspec.json -o form.pdf |
| フォームレイアウトのlint / オーバーレイ画像 | pure python + PIL | pdf_form_layout.py formspec.json [--render-overlay o.png] |
| ページごとのテキスト | pdfplumber | pdf_read.py f.pdf --text |
| 表 → JSON/CSV | pdfplumber | pdf_read.py f.pdf --tables |
| メタデータ / サイズ / 暗号化 / スキャン | pypdf + pdfplumber | pdf_read.py f.pdf --meta |
| 結合 (+ アウトライン) | pypdf | pdf_merge.py a.pdf b.pdf -o m.pdf |
| 分割 / 抽出 / 回転 | pypdf | pdf_split.py f.pdf --pages 2-5 --rotate 90 |
| フォーム一覧 / 入力 / フラット化 | pypdf | pdf_read.py --fields, pdf_fill_form.py |
| 暗号化 / 復号 (AES-256) | pypdf | pdf_secure.py --encrypt/--decrypt |
| PDFページへの透かし / スタンプ | pypdf | pdf_watermark.py f.pdf --stamp w.pdf |
| 座標指定でのテキスト/画像スタンプ | reportlab + pypdf | pdf_stamp.py f.pdf --text "ここに署名" --x 400 --y 60 |
| ページ → PNG(レビュー / OCR受け渡し) | pypdfium2 または pdftoppm | pdf_page_image.py f.pdf --pages 1-3 --out-dir imgs/ |
| メタデータの設定/クリア、添付ファイル | pypdf | pdf_meta.py --set-meta / --attach / --extract-attachments |
| コンテンツストリームの圧縮 | pypdf | pdf_split.py f.pdf --pages 1-N --compress |
手順
- 最初に検査する。
pdf_read.py file.pdf --metaを実行する。encrypted(trueの場合は最初にpdf_secure.py --decryptで復号する)とlikely_scanned_pagesを確認する。ページが画像のみの場合は、pdf_page_image.py --pages <スキャンページ> --dpi 300 --out-dir imgs/でエクスポートし、PNGをreferences/ocr-extraction.mdスキルに渡す — 空のテキストを「コンテンツなし」と報告しないこと。 - 作成する。
write_fileでJSON仕様を記述する(要素:heading、paragraph、table、image、pagebreak。オプションでtitle/authorメタデータ。ページ番号は自動的に追加されます)。その後pdf_create.pyを実行する。レイアウトが重要な場合は、レンダリングされたページ画像でvision_analyzeを使って視覚的に確認する。 - 抽出する。
--textはページごとの文字列のJSONリストを返す。--tablesはページごとに行の配列を返し、CSVファイルも出力できる。結果はread_fileで読み取る。バイナリPDFを直接目視しないこと。 - 操作する。
pdf_merge.pyはファイルを連結し、ソースファイルごとに1つのブックマークを追加できる。pdf_split.pyはページ範囲(1ベース。例:1-3,5,9-)、90度単位の回転、--compressを処理する。透かしは、単一ページのスタンプPDF(例:pdf_create.pyで作成)を準備し、pdf_watermark.pyでオーバーレイする。1行スタンプ(「ここに署名」、斜めのDRAFT、コーナーラベル)には、テキストまたは画像を明示的な座標で指定してpdf_stamp.pyを使用する。 - フォームを構築する。 1つのフォーム仕様JSON(フィールドは
label_box/entry_boxをPDFポイントで指定 —references/forms.mdを参照)を書き、pdf_form_layout.pyでlintし、報告されたすべての問題を修正し、必要に応じて--render-overlayPNGをvision_analyzeでレビューし(赤 = フィールド名付きの入力ボックス、青 = ラベルボックス)、重なり、位置ずれ、フィールドから離れたラベルについて質問する。仕様 → lint → オーバーレイを問題がなくなるまで繰り返す。 - フォームに入力する。
--fieldsでフィールドを一覧表示し、正確な名前とタイプを把握する。write_fileで{"FieldName": "value"}のUTF-8 JSONを書き(チェックボックスはtrue/falseを受け入れ、ラジオ/選択の値はフィールドのエクスポートオプションと一致する必要があります)、その後pdf_fill_form.pyを実行する。--fieldsで再読み取りして値が反映されたことを確認する。 - メタデータと添付ファイル。
pdf_meta.py --set-metaは Title/Author/Subject/Keywords(DocInfo)を書き込む。--clear-metaはそれらを削除する。--attach/--list-attachments/--extract-attachmentsで埋め込みファイルをラウンドトリップする。 - セキュリティ。 異なるユーザー/オーナーパスワードとAES-256で暗号化する。既知のパスワードを削除するには、
--decryptで暗号化されていないコピーを書き込む。 - 確認する(下記参照)。成功を報告する前に行う。
落とし穴
- スキャンPDF:
extract_text()が空でページ画像がある場合は、テキストレイヤーが存在しないことを意味します。references/ocr-extraction.mdにルーティングしてください。テキストを捏造しないでください。 - フラット化の制限:
pdf_fill_form.py --flattenは pypdf のフラット化サポートを使用し、ウィジェットの表示をページコンテンツに変換します。プレーンテキストフィールドとチェックボックスでは信頼できますが、特殊なウィジェット(リッチテキスト、カスタム表示ストリーム、一部のラジオグループ)ではドロップまたは誤ったレンダリングが発生する可能性があります。フラット化された出力をvision_analyzeで視覚的に確認してください。確実なフラット化が必要な場合は、フォールバックとして外部レンダラー(例: Ghostscript またはpdftoppm+ 再アセンブリ)を使用してください。 - NeedAppearances: 入力後、ビューアーは表示ストリームが存在する場合にのみ値をレンダリングします。入力スクリプトは AcroForm
NeedAppearancesフラグを設定するため、準拠するビューアーはそれらを再生成します。一部の最小限のビューアーはこれを無視します — 表示の忠実性が重要な場合はフラット化してください。 - 非ラテンフォーム値: 値は正しく保存されます(UTF-16)が、フィールドのデフォルトフォントにグリフが不足している可能性があるため、データはラウンドトリップしてもビューアーで空白が表示されることがあります。視覚的に確認するだけでなく、
--fieldsで確認してください。 - 圧縮の期待値:
--compressはコンテンツストリームをデフレートするだけです。通常の節約量は0~20%です。画像が支配的なPDFや既に圧縮されたストリームには効果がありません。画像のダウンサンプリング(Ghostscriptの領域)の代わりにはなりません。 - 許可フラグは強制されない: オーナーパスワードの許可ビット(印刷不可、コピー不可)は儀礼的なリクエストであり、ビューアーが尊重する場合があります。どのライブラリ(pypdfを含む)でも読み取りと削除が可能です。コンテンツを実際に保護するのはユーザーパスワードのみです。許可フラグをセキュリティとして提示しないでください。
- テーブル抽出はヒューリスティック: pdfplumberは罫線/単語の整列からテーブルを検出します。境界線がないテーブルやセル結合されたテーブルでは、
table_settingsの調整や手動のクリーンアップが必要な場合があります。 - ページインデックス: ヘルパーCLIは1ベースのページを受け入れます。pypdf APIは0ベースです。スクリプトが変換を行うため、二重変換しないでください。
- 回転したスタンプテキストの抽出: pdfplumberの行グループ化は回転したグリフを混乱させます(45度の「DRAFT」はバラバラの文字として抽出されます)。回転したスタンプは代わりに pypdf の
extract_text()またはレンダリング画像で確認してください。 - ラジオグループ: reportlab はグループごとに2つ以上の
radio()ウィジェットが必要で、値の入力にはスラッシュ付きエクスポート値("/red")が必要であり、フラット化の忠実性はラジオで最も悪いです —references/forms.mdを参照してください。 - メタデータのスコープ:
pdf_meta.pyは従来の DocInfo ディクショナリのみを書き込みます。埋め込みXMPメタデータ(存在する場合)はそのまま残り、一部のビューアーで異なる値が表示される場合があります。 - PDF/Aは対象外: pypdf/reportlab は準拠したPDF/Aを生成または検証できません。アーカイブ準拠が必要な場合は、
terminalツールを介して Ghostscript を実行してください(例:gs -dPDFA=2 -dPDFACompatibilityPolicy=1 -sColorConversionStrategy=UseDeviceIndependentColor -sDEVICE=pdfwrite -o out.pdf in.pdf適切なICCプロファイルを使用)。その後、veraPDF で検証してください — どちらも外部インストールであり、結果は依然として検証が必要であり、想定ではありません。 - 回転は90の倍数である必要があります。暗号化された入力は、他の操作の前に復号する必要があります。
確認
- 作成/結合/分割後:
pdf_read.py out.pdf --meta—page_countと、回転した場合はページごとのrotationを確認する。 - 抽出後: JSONが空でないことを確認し、既知の文字列またはセルをスポットチェックする。
- フォームデザインループ:
pdf_form_layout.py spec.jsonが終了コード0で終了する必要がある。その後--render-overlay boxes.png --pdf form.pdfを実行し、PNGをvision_analyzeでレビューする(赤 = フィールド名付きの入力ボックス、青 = ラベルボックス)。重なり、位置ずれ、フィールドから離れたラベルについて質問する。仕様 → lint → オーバーレイを問題がなくなるまで繰り返す。 - フォーム構築後:
pdf_read.py form.pdf --fieldsですべての仕様フィールドが正しいタイプとオプションでリストされることを確認する。 - フォーム入力後:
pdf_read.py filled.pdf --fieldsで値を比較する(完全一致、非ASCIIを含む)。 - スタンプ後: テキストを再抽出する(回転スタンプの場合は pypdf)か、
pdf_page_image.pyでページをレンダリングし、vision_analyzeで検査する。 - メタデータ/添付ファイル編集後:
pdf_read.py --meta/pdf_meta.py --list-attachmentsを実行し、添付ファイルを再抽出してバイト比較する。 - 暗号化後:
--metaで"encrypted": trueが表示され、パスワードなしで開くと失敗する。復号後、テキスト抽出は元のものと一致する。 - 視覚的なもの(透かし、フラット化されたフォーム)については、レンダリングして
vision_analyzeで検査する。