Speark Deck等スライドホスティングサイトにアップロードしたスライドの文字がtofuになったり消えたりで困っている皆さん向けに、安全な形にPDFを変換する「PDF Slide Guard」というツールを作ってみた。Dockerイメージも用意したよ。
背景
社内では奨励もあってエンジニアの発表が盛んなのだが、スライドホスティングサイトで公開するにあたってたとえば人気の高いSpeaker Deckに資料のPDFをアップロードした際、「文字が一部化ける(□のtofuになる)」「文字が欠ける」といった悲鳴がよく上がる。

ホスティングサイト側でPDFから画像化するにあたって何かエンコーディングか何か周りな予想はあるが、昔から言われているものの解決はされていないようだ。
ワークアラウンドとして考えられるのは、各ページを画像にしてしまい、それをまとめてPDFにしてしまうことだ。ピクセルデータになってしまえば文字処理のバグにひっかかる心配はない。
しかし、このやり方だとPDFに内包していたテキスト情報も失うことになる。たいていのホスティングサイトではスライドページの下部にテキストダンプしたものも提示するようになっており、これは検索流入に効果があると思われるので、できれば捨てたくないはずだ。
ならば、PDFから各ページのテキスト情報を事前に拾っておき、紙面自体は画像化して、紙面には出ない非表示内容としてテキスト情報を埋め込み直すというソリューションはどうか。
画像化+透明埋め込みの仕組み
実装してみよう。
処理としてはそのまま以下のとおりとなる。
- テキストの抽出
- ページの画像化
- テキストの埋め込み
開発には、PDF系ライブラリの品揃えが良いPythonを採択した。利用したPythonライブラリとしては以下の3つだ。
- PyMuPDF(fitz):テキストの抽出、ページの画像化。
- ReportLab:PDFオーバーレイページへのテキストの埋め込み。
- PyPDF:オーバーレイページの作成、リサイズ、結合。
ReportLabの埋め込みでは、日本語フォント(デフォルトはIPAexゴシック)、CMYKカラーモデルの黒100%かつアルファ値0(完全な透明)、0.1pt(極めて小さい)としている。
日本語フォントを設定しておかないと、日本語の文字がテキストとして埋め込まれない。TrueTypeでもOpenTypeでもよいが、ttcのようなコレクションフォントはエラーになる。
ページ内のテキストを1行で埋め込むようにしている都合で、文字サイズを極小にしている。ページからあふれてしまうと、(スライドホスティングサイトなどが)PDFから再度テキストを抽出したときにあふれた範囲が落ちてしまう。
インストールと実行
スタンドアローンなコマンドから作り始めたものの、pip用のパッケージ化やDockerイメージも実現できたので、リリース物として用意した。
環境構築が不要になるので、Dockerイメージを使うことをお勧めする(IPAexフォントも収録済み)。
- Dockerの場合:
docker pull kenshimuto/pdfslideguard:latestまたはdocker pull ghcr.io/kmuto/pdfslideguard:latestでイメージをpullする。 - pipの場合:リリースページからtar.gzをダウンロードし、
pip install(またはpip3 install)に指定してインストールする。
さて、例として、文字化けすることが報告されているPDF(original.pdf)は、PDFビューア上では何も問題ないように見えるのに、Speaker Deckにアップロードしたものは次のように表示されてしまっている。

このPDFをDockerイメージのPDF Slide Guardで変換してみる。-vオプションはカレントフォルダをコンテナ内の/workフォルダにマウントするDockerのオプションで、カレントフォルダのoriginal.pdfからconverted.pdfへの変換をコンテナ内で実行している。
docker run --rm -v ".:/work" kenshimuto/pdfslideguard \ /work/original.pdf /work/converted.pdf
別のスライドとしてSpeaker Deckにアップロードしてみよう。

良さそうだ。
テキストも表示されている。

画像化する都合上、元のPDFよりはサイズは膨れがちになる。ホスティングサイトのサイズ制限にかかってしまう場合は、-zオプションの倍率を適宜調整されたい。デフォルトは3で、1にするとガビガビになるので、実際には2が下限だろう(2にすると少々眠たい感じになる)。
まとめ
ということで、PDFから各ページのテキスト情報を事前に拾っておき、紙面自体は画像化して、紙面には出ない非表示内容としてテキスト情報を埋め込み直すを実現するツール「PDF Slide Guard」を作ってみた。
スライドホスティングサイトでの表示にお困りの皆さん、ぜひ使って広めてくれると嬉しい。Happy Hacking!
(追記) なお、症状が合致するならこちらのほうが手軽だろう。
開発の裏側など
- 昔からこの手の話になるとGhostscriptとかImageMagickとか使って四苦八苦していたものだ…。
- 実際のところ、テキスト抽出はPopplerのpdftotextのほうが綺麗な印象である。PyMuPDFの
get_textをblocksにしたらどうだろうと思ったら悪化した。Python完結縛りをしないなら、pdftotextを呼んで結果を埋め込んだほうが精度は向上できそうだ。 - PDFMinerライブラリを使うと、位置合わせや文字サイズ合わせも可能である(OCRのような感じになる)。しかし1行の中で分割が頻繁な箇所(たとえばコードハイライトなど)ではテキスト化されない現象が多発し、ちょっとしたパラメータ調整程度では解決できなかった。
- 画像化と透明文字化はAdobe Acrobatのプリフライトでも可能(Readerではない)。Acrobatのほうは透明文字の位置なども綺麗に合わせられて文字落ちもなさそうだった。ただAcrobatが必要な時点で汎用性がないし、プリフライトもデフォルトだとdpiが低いものしか作れず、コピーして必死調整が必要なので、用途としては今回ので十分だろう。
- ちなみにEPUBでもSVGと透明コンテンツという形でなんとかする方法というのが昔からある。Adobe InDesignでも機能が提供されていたが、私が過去に試した当時はその出力のfixed EPUBの作りが全然流通形態に合わなくて見送っていた。
- プロダクト名考えるのむずいね。