PDFが読めないのはバグではなかった — 文字が1文字も入っていない領収書の話

自作の電子帳簿リネーマ(レシートPDFを規則通りの名前に変えて年月フォルダに振り分けるMacアプリ)で、ずっと引っかかっていた領収書がありました。

日付も金額も取引先も、何ひとつ読み取れない。エラーも出ない。ただ真っ白

他の領収書はほとんど正しく認識できているので、「このPDFの書式が特殊なんだろう、正規表現を足せばいけるはず」と思っていました。

結論から言うと、まったく違いました

目次

まず中身を見た

正規表現を書く前に、そもそも何のテキストが取れているのかを確認しました。ここを飛ばさなかったのが、今回いちばん良かった判断だったと思います。

テキスト演算子 BT / ET / Tj / TJ / Tf / Td / Tm : すべて 0
フォント参照 /Font  : 0
画像 /Image        : 0
描画パス           : 143個
直線と曲線 (l / c) : 約 4,000本

BT(Begin Text)も Tj(テキスト描画)もゼロ。フォントの参照もゼロ。にもかかわらず、直線とベジェ曲線が4,000本。

つまり、このPDFには 文字が1文字も入っていない

でも、見た目は普通の領収書

画像に書き出すと、ちゃんとこう読めます。

領収書 ¥12,936(税込) マネーフォワード クラウド 2020年11月30日受領分

でもそれは人間の目にだけです。¥1 も、全部が曲線と直線で描かれた図形。文字の形をした絵であって、文字ではない。

いわゆる アウトライン化されたPDF でした。デザインの世界ではおなじみの処理で、フォントが環境依存で崩れるのを防ぐために、文字を図形に変換してしまう。発行側がフォント埋め込みを避けたい事情があると、こうなることがあります。

つまり、直せる種類の問題ではなかった

ここでようやく腑に落ちました。

PDFライブラリ(pdfrx)が空を返していたのは、バグでも取りこぼしでもなく、正しい結果だったのです。抽出すべき文字が存在しないんだから、空以外に返しようがない。

念のため他のライブラリでも試しましたが、当然すべて空でした。ライブラリを変えても、正規表現をいくら足しても、永遠に読めません。存在しないものは読めない。

だから「直さない」ことにした

ここが今回いちばん悩んだところです。

「読めないなら読めるようにしたい」というのが素直な気持ちです。でも冷静に考えると、この1枚のために抽出ロジックへ手を入れるのは、うまく動いている大多数の領収書を壊すリスクだけを負うことになります。得られるものはゼロなのに。

抽出ロジックは、これまで少しずつ育ててきた部分です。日付のパターンが7種類、金額の優先度が7段階。ここを触るのは、正直こわい。そして今回に関しては、触ったところで何も解決しない

なので、直さないという判断をしました。

代わりにやったこと:理由を表示する

とはいえ、真っ白なまま放置はしません。困るのは「読めないこと」そのものより、なぜ読めないのか分からないことだからです。

真っ白な画面を見た自分は、「アプリが壊れた?」「読み込みに失敗した?」「PDFが壊れてる?」と毎回考えてしまう。原因が分かっていれば、迷わず手入力に切り替えられます。

そこで、抽出結果が空のときだけ、こう表示するようにしました。

このPDFからはテキストを抽出できませんでした。

文字が図形化(アウトライン化)されているか、スキャン画像のみのPDFの可能性があります。この場合、PDF内に文字データが存在しないため自動読み取りはできません。

日付・金額・取引先を手入力してください。入力後は通常どおりリネーム・保存できます。

この分岐は、抽出テキストが空のときにしか走りません。正常に読めているPDFは、そもそもこの分岐に入ることすらない。つまり構造的に、既存の動作を壊しようがない変更です。

今まさに真っ白になっているケースが、真っ白+説明文になるだけ。それだけ。

そして大事なのは、読み取り失敗が行き止まりにならないことです。手入力すれば、そのままリネームして保存できる。アプリとしての仕事は最後まで果たせます。

OCRは、いつかやる箱へ

根本的に読むなら、画像に書き出してOCRするしかありません。macOSなら Vision framework が無料・オフライン・日本語対応で使えるので、技術的には可能です。

ただしプラットフォームチャネルを書く必要があり、独立した1プロジェクトです。リリース直前のバージョンに混ぜるものではない。今回は保留にして、CHANGELOGに「なぜ保留にしたか」を書き残しました。

学んだこと

まず中身を見る。 正規表現を1行書く前に、実際に何が取れているかを確認する。今回それをせずにパターンを足し続けていたら、絶対に当たらない的を延々と撃ち続けていました。

直さない判断も、判断のうち。 直せない問題に対して、なぜ直せないかを理解した上で「直さない」と決めるのは、諦めとは違うと思っています。原因が分かったからこそ、代わりに何をすべきかも決まりました。

分からないことより、分からない理由が分からないことの方がつらい。 これはアプリに限らず、そうかもしれません。


半年後の自分がこの領収書をまた開いて悩まないように、CHANGELOGにも調査の経緯を全部残しました。未来の自分は、たぶん忘れているので。

目次