Adobe PDF Embed API を使用して、OCRで取得した座標を元にハイライト(アノテーション)を表示しようとした際に、座標がズレる問題に直面しました。
この記事では、正規化されたOCR座標(1000x1000)を、Adobe APIが要求するポイントベースの座標系へ正しく変換するためのロジックと、ハマりどころを解説します。
背景:何をしようとしていたか
現在、AIが回答の根拠となったPDFの該当箇所を自動でハイライトするUIを構築しています。
実装したいフロー
- AIがPDFの内容を元に回答を生成
- 回答と一緒に「根拠となるテキストの座標データ(OCR結果)」を取得
- ユーザーが回答内のリファレンスをクリックすると、PDFビューア側で該当箇所を自動ハイライトする
このフローを実現するために、「OCRで解析した座標」を「Adobe PDF Embed API のアノテーション用座標」に変換する必要がありました。
しかし、単純に座標を渡すだけでは表示位置が大きくズレてしまうという課題にぶつかり、今回の解決策に至りました。
開発環境・使用技術
今回の実装で使用している主な環境は以下の通りです。
- Framework: Next.js (App Router)
- PDF Viewer: Adobe PDF Embed API
- Backend / Database: Supabase (OCR結果をJSON型で保存)
- OCR: Google Cloud Vision API (または同等の正規化座標を返すエンジン)
1. 座標系の違いを理解する
まず、扱う2つの座標系の違いを明確にします。
-
OCR座標(例: Google Vision APIなど):
- 一般的に、画像やPDFの各辺を 0 〜 1000 で正規化したもの。
- ページサイズ(A4, レターなど)に依存せず、常に一定の割合で表現されます。
-
Adobe PDF Embed API(PDFポイント座標):
- PDFの内部的な「ポイント」単位。
- 一般的な A4 サイズであれば、 72dpi 換算で
595.28 x 841.89ポイントとなります。 -
重要: PDFの仕様上、左下を原点
(0,0)とする場合がありますが、Adobe APIのaddAnnotationsで扱うときは左上を原点として計算するのがスムーズです。
2. 変換ロジックの実装
基本的な変換式
OCRから得られた x, y をPDFの実寸 width, height にスケーリングします。
const scaleX = pdfWidth / 1000;
const scaleY = pdfHeight / 1000;
const actualX = normalizedX * scaleX;
const actualY = normalizedY * scaleY;
Adobe API 形式への整形
Adobe API に渡すアノテーションデータ(Web Annotation準拠)では、quadPoints と boundingBox の両方が必要になることが多いです。
// quadPoints は [x1, y1, x2, y2, x3, y3, x4, y4] の形式
// 順序: [左上, 右上, 左下, 右下]
const quadPoints = [
rect.left, rect.top, // TL
rect.right, rect.top, // TR
rect.left, rect.bottom, // BL
rect.right, rect.bottom // BR
].map(val => val * scale);
3. 実践:動的なページサイズの取得
PDFは全てのページが同じサイズとは限りません。Adobe APIの getPDFMetadata() を利用して、表示中のPDFから動的に寸法を取得するのがベストです。
previewPromise.then(adobeViewer => {
adobeViewer.getAPIs().then(apis => {
apis.getPDFMetadata().then(metadata => {
// metadata.pages[n].numPointsWidth などを利用してスケーリング
});
});
});
4. ハマりどころ:回転とオフセット
PDF自体に回転情報(90度、180度など)が含まれている場合、Adobeのビューアがそれを自動で補正して表示していても、内部的な座標系は回転していないことがあります。
-
対策:
metadataに含まれる回転フラグを確認し、必要に応じてxとyを入れ替えたり反転させたりする処理が必要です。
まとめ
Adobe PDF Embed API でのハイライト表示は、「正規化座標からの復元」 と 「動的なページサイズの考慮」 が鍵となります。
この辺りの処理をラップするユーティリティ関数を作っておきましょう。