見出し画像

SVFで出力したPDFの座標が思てたんと違う

業務システムでお世話になる確率の高い、帳票基盤システムのSVFをご存知でしょうか。
出力したPDFに後からページ番号を付加したりとか、そういったことをしたいときに困ったことと、解決までのお話です。

前提

2024年現在、PDFを取り扱うための有名なライブラリだと、iTextやPDFBoxなどが候補になるかと思います。
PDFBoxはライセンスがApacheで、業務アプリなどでも非常に使いやすいと思います。
今回はPDFBoxを採用したケースです。
あとフォントが化ける問題もありますが、それについては以下をご参照ください。

指定した位置に文字が書けない原因

核心です。
SVFはPDFを出力する際、デフォルトでDPI=400とします。
このPDFに新たに何か追加しようとすると、
PDFを取り扱う際の解像度のデフォルトは72dpiだそうですが、SVFが出力するPDFのように400dpiと設定されている場合、想定していた位置の数値に対して0.18が掛け算されてしまいます。
フォントサイズ20で、と思っていたら、3.6になってしまうといった感じです(ちっちゃい!!!)。
400/72=5.55…倍すれば計算できるけど、そもそもページの文字を追加する場所のDPI設定はいくつか、という想定をしての実装はとても不合理に思えますし、とっても大変かと思います。

処方箋

PDFのページ内のコンテンツには、グラフィックの状態を保持するq/Qのペアのオペレーターを持つことができます。
これを利用して、ページの一番最初にグラフィック状態(デフォルトの72dpi)を保持(qオペレーターを追加)しておき、文字を追加したい個所でグラフィック状態を復活(Qオペレーターを追加)することで、ページ全体に設定された解像度に影響されるのを避けることができるようになります。
グラフィック状態を保持/復活させる、具体的なPDFBoxのメソッドは以下の2つです。

* PDPageContentStream#saveGraphicsState();
* PDPageContentStream#restoreGraphicsState();

qオペレータの状態を調べて、なかったら追加して、などの手順を踏む必要があるので少々面倒ですが、これらをやってくれて、用紙の好きなところに文字を追加するものをGitHubで公開しました。
文字だけじゃなくて、画像などいろいろ足したい場合などにも応用することができますので、以下よりご確認の上お役立てください。
https://github.com/yu1row/PDFBoxUtil

使い方の例

target.pdfの1ページ目、一番下の中央に「Center text」という文字列を追加します。

// ファイルを開いて1ページ目を取得
InputStream is = new FileInputStream("./pdf/target.pdf");
PDDocument doc = PDDocument.load(is);
PDPage page = doc.getPage(0);

// ページの一番下、中央に文字列を追加
EnumSet<PDFBoxUtil.TextWritePosition> pos = EnumSet.of(PDFBoxUtil.TextWritePosition.BOTTOM, PDFBoxUtil.TextWritePosition.CENTER);
PDFBoxUtil.writeText(doc, page, "Center text", PDType1Font.HELVETICA, 10, pos, 0, 0);

// PDFを保存
doc.save(outfile);
doc.close();

この記事が気に入ったらサポートをしてみませんか?