はじめに
anyで業務委託としてエンジニアをしている稲垣です。
この記事は any Product Team Advent Calendar2025 21日目の記事になります。
今年のアドカレでも何を書こうか悩んだのですが、anyでの機能開発中に起きた、PDFからのテキスト抽出に関する問題について書こうと思います。
なお本記事は、問題調査の過程でAIとの対話も参考にしながら整理した内容を含みます。
内容に誤りがあれば、ご指摘いただけると幸いです。
発生した問題
TypeScriptのofficeparserライブラリを使ってPDFファイルからテキストを抽出しようとしたときに、以下のような警告メッセージに遭遇しました。
Warning: UnknownErrorException: Ensure that the `standardFontDataUrl` API parameter is provided.
Warning: loadFont - translateFont failed: "UnknownErrorException: Ensure that the `cMapUrl` and `cMapPacked` API parameters are provided."
このエラーが出た参照元のPDFファイルは、ChatGPTにPDFファイルの生成をさせたものでしたが、不思議なことに、同じテキストをGoogleドキュメントに貼り付け、PDFとしてダウンロードしたファイルを参照元にした場合は、問題なくテキストを読み込むことができました。
この警告の意味を調べていくうちに、PDFの内部構造やフォントの扱いについて理解が深まったため、PDFのフォント埋め込みとテキスト抽出の仕組みについて整理していきます。
この警告メッセージは何を意味するのか
この警告メッセージは、PDFファイルからテキストを抽出する際に、PDF.js(officeparserが内部的に使用しているライブラリ)がフォントデータやマッピング情報を必要としているが、そのデータが提供されていないことを示しています。
PDFファイルのテキスト表現の仕組み
この警告を理解するためには、PDFがどのようにテキストを「保存」しているのかを知る必要があります。
PDFはテキストファイルとは異なり、Unicode文字列を保存している形式ではなく、フォント・マッピング・描画位置といった情報を組み合わせて文字を「描画」するための形式です。
もう少し具体的に見ると、PDFではテキストは次のような要素の組み合わせで表現されています。
1. 文字コード / グリフ参照
- PDF内では、文字はフォント固有の文字コードとして記録されることが多い
- その文字コードは、CMapを介してフォント内のグリフ(文字形状)に対応付けられる
2. CMap / ToUnicode
- PDF内の文字コードを、フォント内のグリフやUnicodeに対応付けるためのマッピング情報
- 描画用のCMapと、テキスト抽出用のToUnicodeは別物
- pdf.js が要求する cMapUrl は、主に描画用CMapのためのもの
- 標準CJKフォントでは、描画用CMapをPDFリーダー側が持っている必要がある
3. 位置情報(Transform Matrix + フォントメトリクス)
- PDFには、文字の描画位置、回転、スケールなどの情報が含まれる
- 一方で、文字の実際の大きさや字送りは、フォントメトリクスから得られる
- テキスト抽出時には、これらの情報を組み合わせて、改行や文字の連続性をヒューリスティックに判断する
つまり、PDFのテキスト抽出とは、人間にとって自然な文字列を後から推測する処理にすぎません。
具体例
実際のファイル構造ではなく、テキスト表現のための概念的な表現ですが、PDF内部のデータは、以下のような情報の集まりとなっています。
※ ここでは説明を簡単にするため、PDF内部の文字コードの違いをまとめて「グリフID」と表現しています。
- グリフID: 65, 位置: (100, 200), フォントサイズ: 12
- グリフID: 66, 位置: (115, 200), フォントサイズ: 12
これを文字に変換するには:
- グリフID 65 → CMapで検索 → Unicode U+0041('A')
- グリフID 66 → CMapで検索 → Unicode U+0042('B')
- 位置情報から「AB」という文字列を構築
PDFのフォントの分類
PDFファイル内で使用されるフォントは、大きく次の2種類に分類されます。
- 標準フォント
PDF仕様によって定義された14種類のフォント(Helvetica、Times、Courierなど)です。
これらのフォントは、PDFリーダーが内蔵していることが前提とされています。 - カスタムフォント
標準フォント以外のフォントです。通常はPDFに埋め込まれている必要があります。
実は、先ほどの警告メッセージは、この分類それぞれに対応して出力されています。
- Warning: UnknownErrorException: Ensure that the standardFontDataUrl API parameter is provided.
→ 標準フォントに対する警告 - Warning: loadFont - translateFont failed: "UnknownErrorException: Ensure that the cMapUrl and cMapPacked API parameters are provided."
→ カスタムフォント(今回のPDFでは日本語フォント)に対する警告です
フォントの埋め込み
PDFでは、フォント情報をPDFデータそのものに内包することができます。
これは、閲覧環境に依存せず、描画命令どおりに同じ見た目で表示できるようにするための仕組みです。
フォント情報と呼ばれているものには、主に次のような要素があり、Adobe Acrobatや一般的なPDF作成ツールで作成されるPDFにはグリフの形状データでさえ、内包されているようです。
- フォント名の情報
どのフォントを使用するかを指定します - フォントメトリクス情報
文字の高さ、幅、ベースラインなどの情報で、レイアウト計算に使用されます - グリフIDと位置情報
どの文字をどの位置に配置するかを示します - フォントデータ(グリフの形状データ)
実際の文字の形状を描画するためのデータです
(通常、TrueTypeやOpenTypeフォントには数MB程度のデータが含まれます)
フォント埋め込みやマッピング情報の有無を確認する方法
pdffontsコマンドを使用すると、PDFファイル内で使用されているフォントやマッピング情報と、その埋め込み状況を確認できます。
ケース1: 警告が表示されるPDF
ためしに、警告が表示されるPDFを pdffonts コマンドで調べてみると、以下の出力が得られました。
name type encoding emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
Helvetica Type 1 WinAnsi no no no 2 0
HeiseiMin-W3 CID Type 0 UniJIS-UCS2-H no no no 3 0
各列の意味は以下のとおりです。
- name: フォント名
- type: フォントタイプ(Type 1、CID Type 0など)
- encoding: エンコーディング方式
- emb: フォントが埋め込まれているかどうか
- sub: サブセット埋め込みの有無
- uni: Unicodeマッピング(ToUnicode)の有無
ポイントは、emb と uni がそれぞれ別の問題を表しているという点です。
- emb(フォント埋め込み)
字形データそのものがPDFに含まれているかどうかを示します。
描画の可否やレイアウト再現性に関わります。 - uni(Unicodeマッピング)
グリフIDからUnicodeへの対応関係が定義されているかどうかを示します。
テキストとして正しく抽出できるかどうかに関わります。
そのため、理屈の上では次のようなパターンもありえます。
- emb: yes, uni: no
→ 描画は可能ですが、「どの文字か」を特定できず、テキスト抽出は困難になります - emb: no, uni: yes
→ Unicodeには変換できますが、字形データはビューアや環境に依存します
今回の問題になったPDFは emb: no, uni: no の状態でした
- フォントも埋め込まれておらず、
- Unicode へのマッピング情報も十分ではない
つまり、フォントデータもUnicodeマッピング情報も不足しており、その結果として PDF.js が standardFontDataUrl(フォントデータ)と cMapUrl / cMapPacked(CMapデータ)の両方を要求する警告を出していた、というわけです。
ケース2: テキスト抽出ができるPDF
Google ドキュメントから出力したPDFをpdffonts で調べると次のような結果になりました。
name type encoding emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
BZZZZZ+Lato-Bold CID TrueType Identity-H yes yes yes 12 0
CZZZZZ+OpenSansEmoji-Regular CID TrueType Identity-H yes yes yes 13 0
DZZZZZ+SourceSansPro-Semibold CID TrueType Identity-H yes yes yes 14 0
すべてのフォントがemb: yes, uni: yesとなっており、フォントデータやマッピング情報が埋め込まれていることがわかります。
この違いが、テキストが抽出できるPDFと、抽出できないPDFの違いになっていたのです。
アプリケーション側の対応
多くのPDF作成ツールでは、フォント情報やマッピング情報の埋め込みはデフォルト、または推奨になっていることが多いようです。
そのため、これらの情報がない場合のテキスト抽出に関しては、サービスの性質にもよりますが、エッジケースとして対応を見送る判断も妥当だと考えられます。
今回のケースでは、ChatGPTによって作成されたPDFがReportLabを用いて生成されており、フォント埋め込みやToUnicodeの指定が行われていなかったことが、問題が顕在化した主な原因でした。
さいごに
本記事では、PDFファイルのテキスト抽出において、フォントデータやマッピング情報の埋め込み状況が非常に重要であることを、実際の警告やPDFの構造、PDFファイルの調査を通して説明しました。
PDFの内部構造を理解することで、テキスト抽出時の警告メッセージの意味がわかり、適切な対応方針を判断できるようになります。
それでは、良い年末をお過ごしください!