Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 496|回复: 0

Html to Pdf 的另类解决方案

[复制链接]
  • TA的每日心情
    奋斗
    4 天前
  • 签到天数: 802 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726006
    发表于 2021-6-4 18:19:36 | 显示全部楼层 |阅读模式

    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文档。
    选择打印机

    图一:选择打印机
    输入xps路径

    图二:输入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,这里不再说明

    有图有真相
    生成xps预览

    有关自动选择XPS Document Writer的hook代码我还没完成,各位赐教!
    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-11-15 10:42 , Processed in 0.654904 second(s), 27 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表