PDFをXPSドキュメントに変換するコードをC#で書く

前回のエントリではPDFのページそれぞれを画像に変換しましたが,いかんせん,CPU負荷の大きさと処理時間の長さが問題でした.
ここではPDFファイルをXPSドキュメントに変換してみます.

XPSドキュメント

XML Paper Specification (XPS) は電子ペーパーの形式です http://www.microsoft.com/japan/whdc/xps/default.mspx
データ形式が公開されており,ライブラリが充実していて,誰でもライブラリを入手でき,かつメタデータの追加などの操作が容易にできる利点から,(現時点では実行環境がWindowsに限定されますが)アプリケーション開発に便利な形式です.

変換方法

.net framework 3.0以降がインストールされた環境には,"Microsoft XPS Document Writer" という仮想プリンタがあります.アプリケーション・ソフトウェアからこの仮想プリンタに印刷すると,その内容をXPSファイルに保存することができます.ここではAdobe Acrobat readerからこのプリンタに印刷をして,PDFファイルからXPSファイルを生成します.

どんなコード?

処理の流れは昨日のエントリと同じです.Microsoft XPS Document Writer に印刷すると保存ファイル名を入力するダイアログが表示されます.Printing documents to Microsoft XPS Document Writer without user interaction – Feng Yuan (袁峰)には,このダイアログを他のアプリケーションから操作するC++のコードがあります.これをC#に書き換えてみました.

動作環境の制約

ダイアログの取得や,ダイアログにあるセーブボタンなどを取得するために,日本語のテキストを手がかりに使っています.このため(英語圏など)ロケールが違う環境では動作しません.英語圏でも使いたいなら,spy++を使いウィンドウの要素を調べて,文字列を適当なものに置換してください.

詳細

細かいところは前回のエントリとほとんど同じです.
コードはこんなの.いかりゃく.

using System;
using System.Text;
using System.Collections.Generic;

using System.ComponentModel;
using System.Runtime.InteropServices;

namespace pdf2xps
{
    /// <summary>
    /// PDFファイルをXPSファイルに変換
    /// 2008/07/12 Akihiro Uehara    
    /// </summary>    

    public class Program
    {

        [DllImport("user32.dll")]
        static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32.dll")]
        static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfeter, string lpszClass, string lpszWindow);

        [DllImport("user32.dll")]
        static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, String lParam);
        
        [DllImport("user32.dll")]
        static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);


        static void Main(string[] args)
        {
            string pdf_filepath = @"c:\tmp\test.pdf"; // 変換するPDFファイル
            string xps_filepath = @"c:\tmp\test.xps"; // 変換するPDFファイル
            const uint WM_SETTEXT = 0x000c;
            const uint WM_IME_KEYDOWN = 0x0290;
            const uint WM_LBUTTONDOWN = 0x0201;
            const uint WM_LBUTTONUP   = 0x0202;


            // PDFファイルを印刷
            Console.WriteLine("Acrobat readerを起動");
            System.Diagnostics.ProcessStartInfo psInfo = new System.Diagnostics.ProcessStartInfo();
            psInfo.FileName = @"C:\Program Files\Adobe\Reader 8.0\Reader\AcroRd32.exe";
            psInfo.Arguments = String.Format(@" /s /h /t {0} ""Microsoft XPS Document Writer""", pdf_filepath); // ファイル名は適切なPDFファイルを指定,オプション詳細は http://scripting.cocolog-nifty.com/blog/2006/12/pdf_4c95.html を参照.
            psInfo.CreateNoWindow = true; // コンソールを開かない
            psInfo.UseShellExecute = false; // シェルを使用しない
            System.Diagnostics.Process ps = new System.Diagnostics.Process();
            ps.StartInfo = psInfo;
            ps.Start();

            System.Threading.Thread.Sleep(5 * 1000); // プリントダイアログをまつ

            // ファイル保存ダイアログボックスを見つける
            IntPtr hWnd = FindWindow("#32770", "ファイル名を付けて保存");
            IntPtr hChild;
            hChild = FindWindowEx(hWnd, IntPtr.Zero, "ComboBoxEx32", String.Empty);
            hChild = FindWindowEx(hChild, IntPtr.Zero, "ComboBox", String.Empty);
            hChild = FindWindowEx(hChild, IntPtr.Zero, "Edit", String.Empty); // File name edit control
            // ファイル名を設定する
            SendMessage(hChild, WM_SETTEXT, IntPtr.Zero, xps_filepath);
            System.Threading.Thread.Sleep(1000);
            // Saveボタンを見つける
            hChild = IntPtr.Zero;
            hChild = FindWindowEx(hWnd, IntPtr.Zero, "Button", "保存(&S)");
            // Saveボタンを押す
            PostMessage(hChild, WM_LBUTTONDOWN, IntPtr.Zero, IntPtr.Zero);
            PostMessage(hChild, WM_LBUTTONUP,   IntPtr.Zero, IntPtr.Zero);
            //SendMessage(hChild, WM_IME_KEYDOWN, 'S', string.Empty);

            // つぎに,プリンタジョブがなくなるのを確認            
            System.Printing.LocalPrintServer prtSrv = new System.Printing.LocalPrintServer();
            System.Printing.PrintQueue queue = prtSrv.GetPrintQueue("Microsoft XPS Document Writer");

            Console.WriteLine("ジョブ完了を確認");
            do
            {
                System.Threading.Thread.Sleep(1000);   // 処理待ち,                
                queue.Refresh();// キューの最新情報を読み込み
            } while (queue.NumberOfJobs > 0);

            Console.WriteLine("完了");
        }
    }    
}