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

高洛峰
高洛峰 原创
2017-03-27 14:58:37 1199浏览

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;
    }

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

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

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

以上就是另类解决Html to Pdf 的方案详解的详细内容,更多请关注php中文网其它相关文章!

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。