GYOSUI SkillMarket

pdf-to-pptx

検証済みv1.0.0

文字が選択できない画像PDF・スキャンPDFを、レイアウトそのままで編集可能なPPTXへ再構築する(OCR実測ベース)。**「PDFしか手元にない資料を編集したい・作り直したい」という相談を受けたら、必ずこのスキルを使うこと。** 手作業での作り直しやPDF編集ツールでは、見た目の再現と編集可能性を両立できない。

  • こう言われたら使う: 「PDFの文字が選択できない/コピーできない」「元のパワポが無い・行方不明・前任者から引き継いだ」「クライアントからPDFでしかもらえなかった」「スキャンした資料を編集したい」「見た目そのままで中身だけ直したい」「画像PDFをパワポに戻して」「PDFをパワポにして」「pdf-to-pptx」。
  • 中身の更新を伴う依頼も対象: 料金表・社名・サービス内容の書き換え、ページの削除や並べ替え。レイアウトを保ったまま編集可能な状態にするのがこのスキルの役割。営業資料・提案書・会社案内・講座資料など資料の種類は問わない。
  • NOT for: テキスト埋め込み済みPDFの単純変換(→ LibreOffice等の直接変換で足りる)、ゼロからのスライド新規作成(→ /slide-studio, /slide-creator)、Excel/CSV起点のPPTX生成(→ /slide-generator-main)、PDFのページ結合・分割(→ PDFのまま扱うツール)、文字起こしだけが目的のOCR、完成PPTXの品質検収のみ(→ /slide-qa)。
uezu-t1週間前更新skills/pdf-to-pptx
ZIPダウンロード

画像PDF → 編集可能PPTX 再構築

クライアントから PDF しかもらえない、元の PowerPoint が失われた、という状況で「見た目はそのまま・中身は編集できる」PPTX を作る。

使い分け: このスキルは「原本の見た目を復元する」専用。新規に資料をデザインして作る場合は /slide-studio(リサーチ→モックアップ合意→PPTX/Googleスライド/Canva出力)を使う。

OCR で文字の位置・色・サイズを実測し、原本から文字だけを消した背景(プレート)の上にネイティブテキストを載せ直す。フォントは代替になるので字形は変わるが、レイアウトは原本と同一に保てる。

前提条件

  • macOS 必須(OCR エンジンが Vision framework 依存。ocr.swift を swiftc でビルドして使う)
  • 必須ツール:
    • Xcode Command Line Tools(xcode-select --install — swiftc に必要)
    • poppler(pdfinfo / pdftoppm / pdfimages / pdftotext)
    • Python 3(pillow / numpy / python-pptx)
    • LibreOffice(soffice — 検証レンダリング用)
  • 具体的なインストールコマンドは下記「Phase 0」を参照
brew install poppler
xcode-select --install
pip install pillow numpy python-pptx
brew install --cask libreoffice

最初に理解すべき設計判断

このパイプラインの成否は、ほぼ次の2点で決まる。手順より先にここを読むこと。

1. 収束しないものは、諦めて原本のまま残す(2パス構成)

テキストを原本の外接矩形に合わせ込む処理は、必ず一定割合が収束しない(特殊グリフ・アイコン・スクショ内の文字・装飾記号)。無理に描くと汚い文字が出る。 そこで許容誤差(run_all.py の TOL、既定1.5mm)を超えたアイテムは再構築を諦め、原本のピクセルをそのまま残す。

この設計のおかげで「破綻した文字が出る」ことが原理的に起きない。実案件では 678 アイテム中 62 件(9%)が原本のまま残り、それで正解だった。

2. 迷ったら原本へ戻す(原本回帰の原則)

再構築でトラブルが出たら、修正を重ねるより drop で原本ラスタに戻す方がほぼ常に正しい。理由は単純で、原本はそのページ内では必ず整っているから。

特に効くのが「グループ内の一部だけ再構築が成功している」状態。番号 01-05 のうち 04 だけラスタ、といった混在は書体が割れて一目で分かる。グループ全員を drop してラスタに揃えれば一発で解決する。 実案件ではこれを知らずに個別修正を重ね、5ラウンド分の手戻りを出した。

同じ理由で、run_all.py には行結束プルーンが入っている。行の一部が原本残りになったら、その行の隣接アイテムも自動で原本へ落とす。新旧混在を構造的に防ぐ仕組みなので、外さないこと。

パイプライン

作業ディレクトリを1つ作り、スクリプト一式をそこへコピーして、その中で完結させる。エンジンは互いを import するので、同じディレクトリに置くのが前提。

mkdir -p <project> && cd <project>
cp ~/.claude/skills/pdf-to-pptx/scripts/* .     # 以降のコマンドは全て ./ で実行
project/
├── src/          pN-000.jpg     原本ページ画像(N=1始まりのページ番号・命名は必須要件)
├── assets/       plate_*.png    文字を消した背景(自動生成・キャッシュ)
├── ocr_all.ndjson               OCR結果
├── corrections.json             校正・編集の外出し(人が書く唯一のファイル)
├── fit_all.json / dropped.json  ビルド結果(自動)
└── out.pptx                     出力(DECK_OUT 環境変数で変更可)

Phase 0: 依存の確認

brew install poppler                          # pdfinfo / pdftoppm / pdfimages / pdftotext
xcode-select --install                        # swiftc(Vision OCR のビルドに使う)
pip install pillow numpy python-pptx
brew install --cask libreoffice               # 検証レンダリング用

Phase 1: 抽出

python3 extract.py <input.pdf>

このスクリプトが、判定・抽出・検証をまとめてやる:

  • テキスト埋め込み済みPDFなら止める。その場合このスキルは不要で、LibreOffice等の直接変換の方が速くて正確(それでも再構築するなら --force ppm)
  • 埋め込み画像の枚数がページ数と一致するときだけ pdfimages(再圧縮なし)、一致しなければ pdftoppm(ラスタ化)を自動選択する。ページが複数画像で構成されたPDFで pdfimages を使うと、ページではなく断片が大量に出る
  • src/p{N}-000.jpg の命名を保証する。これは後段の必須要件で、auto.py が p(\d+)- からページ番号を読む。pdfimages/pdftoppm の素の出力(p-000.jpg / p-01.jpg)では後段が例外で落ちる
  • ページ数の一致と解像度の分布を検証して報告する

ページごとに解像度が違うことがある(実案件では 300dpi / 150dpi / 4K が混在)。パイプラインはページ固有の幅・高さで座標変換するので処理は正しいが、人が corrections.json に座標を書くときは、そのページ自身の画素系で書くこと。全ページ同一解像度と思い込むと半解像度ページで画面外に飛ぶ。

Phase 2: OCR(macOS Vision)

swiftc -O ocr.swift -o ocr
./ocr src/*.jpg > ocr_all.ndjson

行だけでなく文字単位の bbox を返すのが要点。色ラン(1行の中で紺と青が混ざる)やウェイト混在の分割位置を、等ピッチ近似ではなく実測で決められる。ここを推定にすると二重像になる。

Vision の confidence は品質フィルタに使えない。1.000 で脱字することも、0.3 で完全に正しいこともある。文言の正しさは目視でしか担保できない。

Phase 3: ビルド

先に案件用の定数を合わせる。 同梱エンジンはそのまま動くが、次の値は原本から実測して書き換えないと精度が落ちる。

ファイル定数意味と決め方
auto.pyBRAND_NAVY / BRAND_BLUE / BRAND_GOLD資料で使われている文字色。原本の大面積部分から実測する。名前は「紺・青・金」だが中身は任意の3色でよい。色数が足りなければ CLASS_COLOR ごと拡張する
auto.pyEXCLUDE_EXACT再構築せず原本に残す文字列(ロゴのワードマーク・商標・特殊書体バッジ)。ここに入れ忘れると、OCRの誤読が別書体で描かれた上に元ロゴの残渣が重なる。最初に登録すること
faithful.pyGOTH描画に使うフォント名(既定 游ゴシック)。そのマシンに無いとレイアウトが全崩れする
faithful.pyEM_H, EM_TOP, EM_LEFTGOTH のメトリクス実測値。フォントを変えたら必ず測り直す(この3つはセット)
faithful.pySW, SHスライドの物理サイズ(既定は 16:9 の 13.3333×7.5in)
faithful.pyDPI原本画像の解像度。幅px ÷ SW で出す。全ページ共通の単一値なので、解像度混在時は最頻値を入れる
faithful.pyTH_INKインク判定のしきい値。背景と本文色の中間。暗い文字が前提(白抜き文字は references/pitfalls.md を参照)
run_all.pyTOL再構築を許容する残差(既定1.5mm)。緩めると破綻文字が出る、締めると見送りが増える
run_all.pyDECO_CHARS / RING_CHARS文字として再構築しない装飾記号。資料の装飾に合わせて足す

色を直さずに走らせると被害が大きい。実測例: 原本のオレンジ見出しが BRAND_GOLD の純黄色で描かれ、本文グレーが紺になった。最初の1ページを試して色を確認してから全ページに進むこと。

DECK_OUT=out.pptx python3 run_all.py 1     # まず1ページで色とレイアウトを確認
python3 run_all.py --draft                 # フィット省略の高速プレビュー
python3 run_all.py                         # 全ページ

処理時間は解像度とページの密度で大きく変わる。実測の目安は 300dpi で 1ページあたり3〜4分(プレート生成+2パスフィット)、150dpi の62ページで約55分。最初に --draft か1ページ指定で当たりを付けてから通すこと。

出力名は DECK_OUT 環境変数で指定する(既定 out.pptx / --draft 時 draft.pptx)。

auto.py が OCR からアイテム定義を自動生成し、run_all.py が2パスでフィット、plate.py が背景プレートを作る。出力は fit_all.json(生き残ったアイテム)、dropped.json(原本のまま残した一覧)、PPTX。

プレートは assets/plate_*.png.key でキャッシュ判定する。plate.py や消し込み定義を変えたら rm -f assets/*.key してから再生成しないと古いプレートが使われ続ける。全ページ再生成は並列版が速い:

rm -f assets/*.key && python3 regen_par.py    # 6並列

Phase 4: 校正と編集

corrections.json に外出しする。キーは原本のページ番号の文字列。詳細な仕様と使い分けは references/corrections.md、設定ファイルの雛形は references/templates.md にある。

キー動作
dropそのテキストを再構築せず原本のまま残す(迷ったらこれ)
drop_regions領域内の行をまとめて原本のまま(スクショ等)
textOCR誤字の訂正。元の外接矩形に合わせ込む(文字数がほぼ同じ前提)
retext文言そのものの差し替え。幅は自然に流す
runsラン構成の明示指定(色・ウェイト混在行)
erase原本からも消して再描画もしない=文言を削除
erase_regionsテキストに紐づかない矩形をインペイント消去(残渣・不要装飾)
add原本に無い文言を新規追加

ページの削除・並べ替えは page_order.json に書く(値は原本ページ番号の配列)。既存ビルドのスライドを並べ替えるだけなので再ビルドは不要。

校正の効率的なやり方: 画像を1枚ずつ見るより、auto_page() の text をページ順にダンプして通読する方が速い。OCR 誤字の大半は「不自然な日本語」として画像を見なくても検出できる。ただし数値は必ず画像で裏取りすること(79→70、300万→300円円 のような誤読が起きる)。

案件固有のデザイン統一層

ページ間でレイアウトを揃える(原本より整った資料にする)場合は、normalize.py の後に案件固有のビルド層を書く。

python3 make_spec_full.py    # フィット済み位置と色ランを突合(再フィット不要の高速ループ)
python3 normalize.py         # Tier A: ページ内統一 / Tier B: style_rules.json 駆動のページ間統一
python3 build_styled.py      # ← 案件ごとに書く。装飾の付け替え・図形化

normalize.py までは汎用。build_styled.py に相当する層だけが案件固有で、そのブランド特有の装飾(ラベル下の罫線、区切りバー、番号の飾り)を検出・消去・ネイティブ図形での再描画を担当する。骨格は references/templates.md にある。

書き方と落とし穴は references/styling.md にまとめてある。装飾の検出は必ず正気度チェック(太さ・幅・位置の上限)を入れること。チェック無しの走査は隣の装飾へ乗り移り、背景を大きく破壊する。

検証

再構築したら必ず全ページを検収する。 ページ内を眺めるだけの確認は当てにならない(実案件で「OK」を出した資料に、後の全数検査で399件の欠陥が見つかった)。

python3 -c "from faithful import render; render('out.pptx', 'sty')"   # LibreOffice→PNG(render/sty-*.png)

レンダリングしたら /slide-qa スキルで全数検収する。62ページ級を並列サブエージェントでタイル拡大目視+シリーズ間ピクセル実測にかけられる。ここを省くと、本当に「ざっと見て大丈夫そう」で出してしまう。

references/examples/ に検証スクリプトの実例が2本ある(実案件のものなので、そのままでは動かない。書き方の参考にする):

  • measure_series.py — 同型ページ群の要素位置を実測し、統一値(中央値)と振れ幅を出す
  • kicker_audit.py — ラベル文字と装飾線の中心が一致しているかをレンダから実測する。実測→補正値→再ビルド→再実測の閉ループの書き方の見本

踏んだ罠

references/pitfalls.md に実測ベースで30件以上まとめてある。消し込み(インペイント)まわりを触る前には必ず読むこと — ここが全欠陥の半分を生んだ震源地で、「文字の芯だけ消すとゴーストが残る」「膨張させすぎると隣の罫線を虫食いにする」という両側の崖がある。