昨日はスライドホスティングサイトの文字化け・文字落ちの対処としてPDFの画像化・透明文字埋め込みの変換をするPDF Slide Guardを作っていた。
「そもそも何でこの文字化けが起きるんだ?」と掘っていった結果、(少なくとも今対象にしているケースでは)QPDFで修復できることがわかった。
qpdf --qdf 元PDFファイル 修復後PDFファイル だけで治り、画像化によるサイズ問題や埋め込みテキストの消失も考えなくてよい。Dockerイメージも一応用意したよ。
Ghostscriptによる調査
壊れるPDFはそもそも何らかおかしいと思われるので、リファレンスとしてGhostscriptで見てみるところから始めた。以下はPDF original.pdfを読み込んでPDF g.pdfを書き込むという処理である。
$ gs -sDEVICE=pdfwrite -o g.pdf original.pdf GPL Ghostscript 10.05.1 (2025-04-29) Copyright (C) 2025 Artifex Software, Inc. All rights reserved. This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY: see the file COPYING for details. ... Page 6 GPL Ghostscript 10.05.1: Missing glyph CID=0, glyph=6201 in the font MUFUZY+MPLUS2-Bold . The output PDF may fail with some viewers. Page 7 ... The following errors were encountered at least once while processing this file: Attempt to dereference a free object **** This file had errors that were repaired or ignored. **** Please notify the author of the software that produced this **** file that it does not conform to Adobe's published PDF **** specification. $
いかにも怪しいメッセージが出てきた。g.pdfを見ると、該当箇所のtofu化が起きている。

症状としては以下のようになっているのだろう。
- GoogleスライドでM Plus和文フォントを使って文字を入れた
- スライドでPDF書き出しを選び、Google側がPDF変換を行った
- このとき、M Plus和文フォント全部ではなくサブセットフォントが作られ、埋め込まれた
- Ghostscriptは「CID=0, glyph=6201」はグリフ(字形)ID 6201について、文字コード(CID)がゼロまたは無効な参照になっていることを検知した。つまりグリフIDとCIDの対応関係が何かおかしなことになっている(ほかのビューア、PopplerやMuPDFなどは何らか寛容にできる仕組みがある? フォールバックしているという見た目でもないし)
- Ghostscriptは無効部分をtofuなどの表示とした
各ページでの欠落箇所はSpeaker Deckと同じであり、Speaker DeckでもおそらくGhostscriptかそれに類似したエンジンを使っていると推測する。
GhostscriptでPDFを扱う時点で壊れているので、Ghostscriptを使うアウトライン化(-dNoOutputFonts)やPostScript化(-sDEVICE=ps2write)は効果がない。
CIDチェックの無視(-dUseCICP=false)、互換性レベル(-dCompatibilityLevel)、事前処理(-dPDFSETTINGS=/prepress)、CJKオプションまわり(-dCIDfmap=false)も効き目はなかった。
QPDFによる修復
フォント構造のところでおかしいことはわかったので、PDFの正規化を試みることにする。そのような用途には、QPDFというツールがある。
QPDFは多才で修復にもたくさんのオプションがあり、試行錯誤していたのだが、結論としてはQDF形式(非圧縮・リニア)に正規化し直す--qdfオプションだけでよい(しばらく--qpdfとタイプしてはエラーになって悩んでいた)。
以下はPDF original.pdfを読み込んで、正規化したPDF q.pdfを書き込む例である。
$ qpdf --qdf original.pdf q.pdf
Ghostscriptに通してみる。
$ gs -sDEVICE=pdfwrite -o g.pdf q.pdf GPL Ghostscript 10.05.1 (2025-04-29) Copyright (C) 2025 Artifex Software, Inc. All rights reserved. This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY: see the file COPYING for details. ... Page 6 Page 7 ... $
つつがなく終わった。g.pdfを表示してみよう。

Speaker Deckに正規化済みのq.pdfをテスト投稿してみる。

やったぜ。
Dockerイメージの提供
qpdfを単に実行するだけなのでDockerイメージ化するほどでもない気はするが、Docker Hubのkenshimuto/qpdfに作っておいた。
https://hub.docker.com/r/kenshimuto/qpdfhub.docker.com
中身としては、以下のとおり本当にqpdfコマンドを実行しているだけである。
#!/bin/sh qpdf --qdf $1 $2
FROM debian:trixie-slim ENV LANG=en_US.UTF-8 ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update \ && apt-get install -y --no-install-recommends qpdf \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* COPY ./fix-pdf /usr/bin/fix-pdf RUN chmod a+x /usr/bin/fix-pdf ENTRYPOINT ["fix-pdf"]