另类解决Html to Pdf 的方案详解

高洛峰
Lepaskan: 2017-03-27 14:58:37
asal
1641 orang telah melayarinya

Background

项目里要求将一个HTML页面(支付结果)生成pdf文档。页面有图片,有表格,貌似开源的iTextSharp应付不了.

在一番搜索之后,找到了wkhtmltopdf,一个命令行的开源转换工具,支持指定url或本地html file的路径,试用后效果不错,还特意用wkhtmltopdf写了一个工具将博客园的帖子备份pdf到本地,后续有空把这个工具分享出来

But,发给客户测试两天运行效果不太理想,出现一些未知错误,而且奇怪的是在测试环境没问题,正式环境却频繁出错。最后客户放弃这个方案
附上 WkhtmlToXSharp C# wrapper wrapper (using P/Invoke) for the excelent Html to PDF conversion library wkhtmltopdf library.
***
OK,来到正题,另类的解决方案:Hook

调用IE打印功能,使用XPS打印机,先将HTML文件生成xps文档,再生成pdf

新建WinForm 项目,拖入WebBrowser控件,代码指定Url到本地html文件路径,等待文档加载完成后 WebBrowser.Print(); OK,运行,会弹出选择打印机的对话框,如图一。点击打印后,弹出另存为的对话框,输入xps路径后保存(图二),即可得到一份xps文档。
另类解决Html to Pdf 的方案详解

图一:选择打印机
另类解决Html to Pdf 的方案详解

图二:输入xps路径

从上面可以看到,这里的打印需要与UI交互,人工点击打印,输入xps路径保存才行。
接下来在网络搜索:怎么不显示对话框,直接打印生成xps文件,在stackoverflow,codeproject看了很多,没找到办法。后来偶然翻到园子前人的文章,采用hook方式,UI Automation来完成打印和保存的动作,觉得这个方案可行

接下来上代码吧

    //调用WebBrowser.Print的代码就忽略了,直接看钩子
    IntPtr hwndDialog;
    string pathFile;
    EnumBrowserFileSaveType saveType;

    // Imports of the User32 DLL. 
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr GetDlgItem(IntPtr hWnd, int nIDDlgItem);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    static extern private bool SetWindowText(IntPtr hWnd, string lpString);
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool IsWindowVisible(IntPtr hWnd);

    //Win32 Api定义
    [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);


    //Win32消息定义
    const uint WM_SETTEXT = 0x000c;
    const uint WM_IME_KEYDOWN = 0x0290;
    const uint WM_LBUTTONDOWN = 0x0201;
    const uint WM_LBUTTONUP = 0x0202;

    // The thread procedure performs the message loop and place the data
    public void ThreadProc()
    {
        int maxRetry = 10;
        int retry = 0;
        IntPtr hWndPrint = FindWindow("#32770", "打印");
        IntPtr hWnd = FindWindow("#32770", "文件另存为");
        if (hWnd != IntPtr.Zero)
        {
            log.InfoFormat("got saveas dialog handle. Printer Dialog skipped.");
        }
        else
        {
            Thread.Sleep(200);
            hWndPrint = FindWindow("#32770", "打印");

            //这里有时候获取不到window,所以加了Sleep,多试几次
            while (hWndPrint == IntPtr.Zero && retry < maxRetry)
            {
                Thread.Sleep(200);
                log.InfoFormat("retry get Print dialog handle.retry:{0}", retry);
                hWndPrint = FindWindow("#32770", "打印");
                retry++;
            }
            if (hWndPrint == IntPtr.Zero)
            {
                //wait 1 second,retry again
                Thread.Sleep(1000);
                hWndPrint = FindWindow("#32770", "打印");
            }
            if (hWndPrint == IntPtr.Zero)
            {
                log.InfoFormat("Did not get Print dialog handle.retry:{0}", retry);
                return;
            }
            log.InfoFormat("got Print dialog handle.retry:{0}", retry);
            //select printer dialog
            IntPtr hChildP;
            hChildP = IntPtr.Zero;
            hChildP = FindWindowEx(hWndPrint, IntPtr.Zero, "Button", "打印(&P)");
            // 向保存按钮发送2个消息,以模拟click消息,借此来按下保存按钮
            PostMessage(hChildP, WM_LBUTTONDOWN, IntPtr.Zero, IntPtr.Zero);
            PostMessage(hChildP, WM_LBUTTONUP, IntPtr.Zero, IntPtr.Zero);
            Application.DoEvents();
        }

        //hWnd = FindWindow("#32770", null);
        hWnd = FindWindow("#32770", "文件另存为");
        //To avoid race condition, we are forcing this thread to wait until Saveas dialog is displayed.
        retry = 0;
        while ((!IsWindowVisible(hWnd) || hWnd == IntPtr.Zero) && retry < maxRetry)
        {
            Thread.Sleep(200);
            log.InfoFormat("retry get saveas dialog handle.retry:{0}", retry);
            hWnd = FindWindow("#32770", null);
            retry++;
            Application.DoEvents();
        }
        log.InfoFormat("got saveas dialog handle.retry:{0}", retry);
        if (hWnd == IntPtr.Zero)
        {
            //wait 1 second,retry again
            Thread.Sleep(1000);
            hWnd = FindWindow("#32770", "文件另存为");
        }
        if (hWnd == IntPtr.Zero)
        {
            return;
        }
        Application.DoEvents();

        IntPtr hChild;
        // 由于输入框被多个控件嵌套,因此需要一级一级的往控件内找到输入框
        hChild = FindWindowEx(hWnd, IntPtr.Zero, "DUIViewWndClassName", String.Empty);
        hChild = FindWindowEx(hChild, IntPtr.Zero, "DirectUIHWND", String.Empty);
        hChild = FindWindowEx(hChild, IntPtr.Zero, "FloatNotifySink", String.Empty);
        hChild = FindWindowEx(hChild, IntPtr.Zero, "ComboBox", String.Empty);
        hChild = FindWindowEx(hChild, IntPtr.Zero, "Edit", String.Empty); // File name edit control
        // 向输入框发送消息,填充目标xps文件名
        SendMessage(hChild, WM_SETTEXT, IntPtr.Zero, pathFile);
        // 等待1秒钟
        System.Threading.Thread.Sleep(1000);
        // 找到对话框内的保存按钮
        hChild = IntPtr.Zero;
        hChild = FindWindowEx(hWnd, IntPtr.Zero, "Button", "保存(&S)");
        // 向保存按钮发送2个消息,以模拟click消息,借此来按下保存按钮
        PostMessage(hChild, WM_LBUTTONDOWN, IntPtr.Zero, IntPtr.Zero);
        PostMessage(hChild, WM_LBUTTONUP, IntPtr.Zero, IntPtr.Zero);

        // Clean up GUI - we have clicked save button.
        //GC is going to do that cleanup job, so we are OK
        Application.DoEvents();
        //Terminate the thread.
        return;
    }
Salin selepas log masuk

接下来有关xps转pdf,使用了Spire.Pdf,官方有demo,这里不再说明

有图有真相
另类解决Html to Pdf 的方案详解

有关自动选择XPS Document Writer的hook代码我还没完成,各位赐教!

Atas ialah kandungan terperinci 另类解决Html to Pdf 的方案详解. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Label berkaitan:
sumber:php.cn
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan