Swanman's Horizon

性欲をもてあましつつなんらかの話をするよ。

外部ライブラリ無しでPDFを描画する。

WinRTって知ってる?


WinRTとは、Windowsストアアプリ専用のAPIセット…ではないです。すでにトーストAPIなどを使っていてご存じの方もいると思いますが、デスクトップからも利用可能なCOMベースのAPIです。
Windows 8の時点だとWinRTは未来があるのか怪しいAPIでしたが、Windows 10がWindowsの最終バージョンであるとMSが宣言し、そのWindows 10に載っているAPIなのでWin32APIと同等に扱っても良さそうです。

WinRTでできること

ストアアプリは基本的にこのAPIを使って構築することになるので、上述のトーストAPIで画面に通知を出すこと以外にもとにかく色んなことができます。
で、その中にはPDFを扱うAPIもあります。こちらのブログ経由で知ったんですが、これを利用するとどうやら外部ライブラリを使わずにPDFを扱えるようです。
WinRTは腰の重いエンバカデロには珍しいことに、すでにラッパーユニットが用意されています。10 Seattle以降では標準搭載、それ以前ではGetIt経由で入手可能です。

しかし…

じゃあこれでPDF APIも使用可能かというと、残念ながらPDF関連のヘッダはほぼ全く移植されてませんでした(関連する列挙型が1つあるだけ)。
無いなら自分でやればいいということで、さくっと移植。これでようやくAPIが使える…かというとそうは問屋が卸さず。
PDF描画の心臓部であるIPdfRendererNativeインターフェースのRenderPageToDeviceContextメソッドで引数として使われているID2D1DeviceContextが存在しないとコンパイラ様が仰るわけです。

怠慢ですよねぇ

DelphiにはWinapi.D2D1ユニットがはじめから入ってます。Direct2D用のユニットです。これはd2d1.h(とそれに必要な他のいくつかのヘッダ)を移植したもので、これ自体には問題ありません*1。実はDirect2Dにはバージョンがあり、Vista/7で導入された1.0、8で導入された1.1、8.1で導入された1.2、10で導入された1.3があります。d2d1.hはこのうち1.0のみを扱っており、1.1はd2d1_1.hが、1.2はd2d1_2.hが、1.3はd2d1_3.hが必要になります。なりますが、Delphiにはそれらを移植したユニットは存在しません。
大事なことなのでもう一度言いますが、Delphiにはそれらを移植したユニットは存在しません。新バージョンが発売されて「WINDOWS 10の最新機能をサポート」なんてエンバカデロは言ってますが、やはりDelphiにはそれらを移植したユニットは存在しません。エンバカデロの「Windows XXに対応!」はセールス的に見栄えのするUI系やセンサーのサポートみたいなのはやってくれるんですけど、そのOSで追加された機能の大半、特に裏方的な機能はほんとに対応してくれません。PDFはまだいいとしても、Direct2DはGDI/GDI+の後継APIでかなり重要なので、こんな状態でサポートとか言うのほんと詐欺なんでやめて欲しい…。

もっと頑張って移植

なんて愚痴を言ってるときりがないので、諦めて自分で移植します。幸いなことにID2D1DeviceContextはDirect2D 1.1で導入されたので、d2d1_1.hだけを移植すれば良さそうです。「だけ」と言いつつ、d2d1_1.hは5000行を超すサイズなのでPDFヘッダを移植する時の手軽さでは全然ないんですが、これも何とか移植しました。

ようやく本編

これでPDF関連のユニット(WinAPI.Data.Pdf.pas, WinAPI.Data.Pdf.Interop.pas)とそれに必要なDirect2D用ユニット(Winapi.D2D1_1.pas)が揃いました。あとは前述のブログを参考に実装するだけです。本質部分とは関係ないDirect2Dの初期化だけで結構量があり、ここに載せるにはちょっと長いので必須ユニットとサンプルをまとめてアップしますが、こんな感じでフォームにPDFを表示できます。

ちなみに表示だけじゃなく右クリックで画像として保存可能ですが、フォーム上に表示するのと画像化では処理が異なり、前者はIPdfRendererNativeとIPdfDocumentインターフェースを、後者はIPdfDocumentインターフェースのみを使っています。前者はID2D1DeviceContextやIDXGISurfaceのように画面上に書き出すためのもので、後者はIRandomAccessStreamなどストリームに書き出すことができます。特に後者はDirect2D等を必要としないので、PdfDocumentのインスタンスさえ作れば簡単に扱うことが可能です。IStreamにも書き出せるので、IStreamを実装してTMemoryStream辺りに書き出すようにして、それをDelphi側の画像クラスに食わせて画面表示した方が全体としては楽そうです。

ダウンロード

ユニットとサンプルをダウンロード
サンプルを試してみる時は、libフォルダをパスに追加するか、中身をパスの通った場所に置いてください。
動作環境ですが、MSDNを見るとWindows 8.1でも使えるのかWindows 10じゃないと使えないのかAPIやページによってまちまちでよく分からないんですが、少なくとも手元のWindows 10+10.1 Berlin上では動いてます。

余談

Direct2Dの初期化も、Direct2D 1.0のID2D1RenderTargetを使ったものはVcl.Direct2D内に実装されているんですが、今はID2D1DeviceContextを使ったものが主流で、例えばWIC(Windows Imaging Component)なんかでID2D1DeviceContextを必要とするメソッドがあるんですよね。起点となるインターフェースなので本当に重要なんです。
まあそのWICのメソッドってのもWindows 8で追加された新しいインターフェースで追加されたもの(かつ未移植)なので、そもそも使えないんですけどね…。

*1:まぁSDKのバージョンアップで更新された部分は全然反映されてないんだけど